diff --git a/.gitignore b/.gitignore index 2db29fa..eb2194a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /*.log /*/bin/ /local.properties -/browser/profile/* +/profile/ +/firefox/ diff --git a/addonInter/build.gradle b/addonInter/build.gradle index 65ac454..93d036c 100644 --- a/addonInter/build.gradle +++ b/addonInter/build.gradle @@ -61,7 +61,7 @@ abstract class BuildManifest extends DefaultTask { startupScript = files.filter(file -> file.getName().endsWith(".bat")).findFirst().get() } else if (Os.isFamily(Os.FAMILY_UNIX)) { - startupScript = files.filter(file -> !file.getName().endsWith(".bat")).findFirst().get() + startupScript = files.filter(file -> !file.getName().endsWith(".sh")).findFirst().get() } else { throw new RuntimeException("Unknown OS family") @@ -116,5 +116,3 @@ run.dependsOn(installAddon) // Define the main class for the application mainClassName = defaultPackage + '.addon.Main' - -compileJava.dependsOn rootProject.bundleAddOn \ No newline at end of file diff --git a/browser/browserConf/defaults/pref/autoconfig.js b/browser/browserConf/defaults/pref/autoconfig.js new file mode 100644 index 0000000..e4ea156 --- /dev/null +++ b/browser/browserConf/defaults/pref/autoconfig.js @@ -0,0 +1,2 @@ +pref("general.config.filename", "firefox.cfg"); +pref("general.config.obscure_value", 0); \ No newline at end of file diff --git a/browser/browserConf/distribution/policies.json b/browser/browserConf/distribution/policies.json new file mode 100644 index 0000000..2a095e9 --- /dev/null +++ b/browser/browserConf/distribution/policies.json @@ -0,0 +1,37 @@ +{ + "policies": { + "DisableAppUpdate": true, + "DisableFeedbackCommands": true, + "DisableFirefoxScreenshots": true, + "DisableFirefoxStudies": true, + "DisablePocket": true, + "DisableTelemetry": true, + "OverrideFirstRunPage": "about:home", + "OverridePostUpdatePage": "about:home", + "EnableTrackingProtection": { + "Value": true, + "Locked": false, + "Cryptomining": true, + "Fingerprinting": true + }, + "DontCheckDefaultBrowser": true, + "Permissions": { + "Camera": { + "BlockNewRequests": true + }, + "Microphone": { + "BlockNewRequests": true + }, + "Location": { + "BlockNewRequests": true + }, + "Notifications": { + "BlockNewRequests": true + }, + "Autoplay": { + "Default": "allow-audio-video", + "Locked": true + } + } + } +} \ No newline at end of file diff --git a/browser/browserConf/distribution/prefs.js b/browser/browserConf/distribution/prefs.js new file mode 100644 index 0000000..979be9b --- /dev/null +++ b/browser/browserConf/distribution/prefs.js @@ -0,0 +1 @@ +user_pref("xpinstall.signatures.required", false); \ No newline at end of file diff --git a/browser/browserConf/firefox.cfg b/browser/browserConf/firefox.cfg new file mode 100644 index 0000000..de0b7a3 --- /dev/null +++ b/browser/browserConf/firefox.cfg @@ -0,0 +1,2 @@ +// Hello! +defaultPref("xpinstall.signatures.required", false) \ No newline at end of file diff --git a/browser/build.gradle b/browser/build.gradle new file mode 100644 index 0000000..a765789 --- /dev/null +++ b/browser/build.gradle @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2021 William Hubbard. All Rights Reserved. + */ + +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + id 'java' + id "de.undercouch.download" version "4.1.2" +} + +// In this section you declare where to find the dependencies of your project +repositories { + mavenCentral() + def mozilla = ivy { + url 'https://download-installer.cdn.mozilla.net/' + + patternLayout { + artifact '/pub/[module]/releases/[revision]/[classifier]/en-US/firefox-[revision].[ext]' + artifact '/pub/[module]/releases/[revision]/[classifier]/en-US/Firefox [revision].[ext]' + artifact '/pub/[module]/releases/[revision]/[classifier]/en-US/Firefox Setup [revision].[ext]' + } + + metadataSources { + artifact() + } + } + flatDir { + dirs new File(rootDir, 'libs') + } + exclusiveContent { + forRepositories(mozilla) + filter { + includeGroup("firefox") + } + } +} + +abstract class WriteBat extends DefaultTask { + @Input + final abstract Property command = project.objects.property(String) + @OutputFile + final abstract RegularFileProperty outputFile = project.objects.fileProperty().convention(project.layout.buildDirectory.file('install.bat')) + + @TaskAction + void join() { + outputFile.get().asFile.text = command.get() + } +} + +def firefox_module = "devedition" +def firefox_revision = "92.0b7" + +task bundleAddOn(type: Zip) { + setArchiveName "universalmusic@regis.edu.xpi" + setDestinationDir file("$rootDir/firefox/distribution/extensions") + from (files("$rootDir/add-on")) +} + +task movePolicies(type: Copy) { + from files("browserConf") + into "$rootDir/firefox/" +} + +task setupProfile { + doFirst { + mkdir "$rootDir/firefox/distribution" + mkdir "$rootDir/firefox/distribution/extensions" + } + finalizedBy movePolicies + finalizedBy bundleAddOn +} + +task downloadWindows_x86_64(type: Download) { + src "https://download-installer.cdn.mozilla.net/pub/${firefox_module}/releases/${firefox_revision}/win64/en-US/Firefox%20Setup%20${firefox_revision}.msi" + dest layout.buildDirectory.file("installer.msi") + overwrite false + onlyIfModified true +} + +task downloadWindows_x86(type: Download) { + src "https://download-installer.cdn.mozilla.net/pub/${firefox_module}/releases/${firefox_revision}/win32/en-US/Firefox%20Setup%20${firefox_revision}.msi" + dest layout.buildDirectory.file("installer.msi") + overwrite false + onlyIfModified true +} + +task downloadLinux_x86_64(type: Download) { + src "https://download-installer.cdn.mozilla.net/pub/${firefox_module}/releases/${firefox_revision}/linux-x86_64/en-US/firefox-${firefox_revision}.tar.bz2" + dest layout.buildDirectory.file("installer.tar.bz2") + overwrite false + onlyIfModified true +} + +task downloadLinux_i686(type: Download) { + src "https://download-installer.cdn.mozilla.net/pub/${firefox_module}/releases/${firefox_revision}/linux-i686/en-US/firefox-${firefox_revision}.tar.bz2" + dest layout.buildDirectory.file("installer.tar.bz2") + overwrite false + onlyIfModified true +} + +task installWindows_x86_64(dependsOn: downloadWindows_x86_64, type: Exec) { + workingDir layout.buildDirectory + commandLine 'msiexec', '/i', '"' + downloadWindows_x86_64.dest + '"', '/li', '"install.log"', '/qb', "INSTALL_DIRECTORY_PATH=\"$rootDir\\firefox\"", 'TASKBAR_SHORTCUT=false', 'DESKTOP_SHORTCUT=false', 'INSTALL_MAINTENANCE_SERVICE=false' + outputs.file("$rootDir\\firefox\\firefox.exe") +} +//installWindows_x86_64.onlyIf { !layout.buildDirectory.file("browser/firefox.exe").get().asFile.exists() } +installWindows_x86_64.doFirst { + println "Administrator privileges needed for installing Firefox. Please confirm on the popup." +} +installWindows_x86_64.finalizedBy setupProfile + +task installWindows_x86(dependsOn: downloadWindows_x86, type: Exec) { + workingDir layout.buildDirectory + commandLine 'msiexec', '/i', '"' + downloadWindows_x86.dest + '"', '/li', '"install.log"', '/qb', "INSTALL_DIRECTORY_PATH=\"$rootDir/firefox\"", 'TASKBAR_SHORTCUT=false', 'DESKTOP_SHORTCUT=false', 'INSTALL_MAINTENANCE_SERVICE=false' + outputs.file("$rootDir\\firefox\\firefox.exe") +} +//installWindows_x86.onlyIf { !layout.buildDirectory.file("browser/firefox.exe").get().asFile.exists() } +installWindows_x86.doFirst { + println "Administrator privileges needed for installing Firefox. Please confirm on the popup." +} +installWindows_x86.finalizedBy setupProfile + +task deleteFirefoxWindows(type: Delete) { + delete "$rootDir/firefox" +} + +task uninstallFirefoxWindows(type: Exec) { + workingDir layout.buildDirectory + commandLine 'cmd', '/c', "$rootDir\\firefox\\uninstall\\helper.exe", '/S' +} +uninstallFirefoxWindows.onlyIf { new File("$rootDir/firefox/firefox.exe").exists() } +uninstallFirefoxWindows.doFirst { + println "Administrator privileges needed for uninstalling Firefox. Please confirm on the popup." +} +uninstallFirefoxWindows.finalizedBy deleteFirefoxWindows + +task installLinux_x86_64(dependsOn: downloadLinux_x86_64, type: Copy) { + from tarTree(downloadLinux_x86_64.dest) + into "$rootDir/firefox" +} +installLinux_x86_64.finalizedBy setupProfile + +task installLinux_i686(dependsOn: downloadLinux_i686, type: Copy) { + from tarTree(downloadLinux_i686.dest) + into "$rootDir/firefox" +} +installLinux_i686.finalizedBy setupProfile + +tasks.named('clean') { + dependsOn uninstallFirefoxWindows +} + +dependencies { + implementation 'org.slf4j:slf4j-api:1.7.30' + implementation 'org.apache.logging.log4j:log4j-api: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 project(":browserCommands") +} + +// In this section you declare the dependencies for your production and test code +if (Os.isFamily(Os.FAMILY_WINDOWS)) +{ + if (Os.isArch("x86_64") || Os.isArch("amd64")) + { + print("Windows x86_64") + processResources.finalizedBy installWindows_x86_64 + } + else if (Os.isArch("x86") || Os.isArch("i386") || Os.isArch("i686")) + { + print("Windows x86") + processResources.finalizedBy installWindows_x86 + } + else + { + printf("Unknown windows architecture %s!\n", System.getProperty("os.arch")) + } +} +else if (Os.isFamily(Os.FAMILY_MAC)) +{ + print("Mac") + processResources.finalizedBy { + api "firefox:devedition:92.0b7:mac@dmg" + } +} +else if (Os.isFamily(Os.FAMILY_UNIX)) +{ + if (Os.isArch("x86_64") || Os.isArch("amd64")) + { + processResources.finalizedBy installLinux_x86_64 + } + else if (Os.isArch("x86") || Os.isArch("i386") || Os.isArch("i686")) + { + processResources.finalizedBy installLinux_i686 + } + else + { + printf("Unknown Linux architecture %s!\n", System.getProperty("os.arch")) + } +} +else +{ + printf("Unknown operating system %s!\n", System.getProperty("os.name")) +} + diff --git a/browser/src/main/java/edu/regis/universeplayer/browser/Browser.java b/browser/src/main/java/edu/regis/universeplayer/browser/Browser.java new file mode 100644 index 0000000..6b4001d --- /dev/null +++ b/browser/src/main/java/edu/regis/universeplayer/browser/Browser.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2021 William Hubbard. All Rights Reserved. + */ + +package edu.regis.universeplayer.browser; + +import edu.regis.universeplayer.browserCommands.BrowserConstants; +import edu.regis.universeplayer.browserCommands.MessageRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Scanner; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Browser extends MessageRunner +{ + private static final Logger logger = LoggerFactory.getLogger(Browser.class); + + private static Browser INSTANCE; + private static final AtomicBoolean instanceWaiter = new AtomicBoolean(); + + public static Browser getInstance() + { + return INSTANCE; + } + + private final Process process; + private final ServerSocket server; + private final Socket socket; + + private boolean running = true; + + public static Browser createBrowser() throws IOException, InterruptedException + { + if (INSTANCE != null) + { + return INSTANCE; + } + + ServerSocket server = new ServerSocket(BrowserConstants.PORT, 50, InetAddress.getByName(null)); + logger.debug("Server started."); + + int startExit; + Process browserProcess = launchBrowser(); + /* + * Wait for the browser to fully start. + */ + startExit = browserProcess.waitFor(); + if (startExit != 0) + { + logger.error("Error in browser launch (exit code {})", startExit); + try (Scanner scanner = new Scanner(browserProcess.getErrorStream())) + { + while (scanner.hasNextLine()) + { + logger.error(scanner.nextLine()); + } + } + throw new IOException("Error in browser launch (exit code " + startExit + ")"); + } + logger.debug("Browser started."); + + ConnectException connErr = null; + logger.debug("Attempting connection"); + Socket socket = server.accept(); + if (!socket.isBound()) + { + logger.error("Socket not bound"); + } + else if (!socket.isConnected()) + { + logger.error("Socket not connected"); + } + else if (socket.isClosed()) + { + logger.error("Socket prematurely closed"); + } + else if (socket.isInputShutdown()) + { + logger.error("Socket input prematurely closed."); + } + else if (socket.isOutputShutdown()) + { + logger.error("Socket input prematurely closed."); + } + else + { + logger.debug("Connection established."); + } + INSTANCE = new Browser(socket, server, browserProcess); + instanceWaiter.set(true); + notifyAllInstance(); + return INSTANCE; + } + + private Browser(Socket socket, ServerSocket server, Process process) throws IOException + { + super("BrowserRunner", socket.getInputStream(), socket.getOutputStream()); + this.socket = socket; + this.server = server; + 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 !this.running; + } + + @Override + protected void onClose() + { + logger.debug("Closing socket"); + this.running = false; + try + { + this.socket.close(); + } + catch (IOException e) + { + logger.error("Could not close socket", e); + } + finally + { + logger.debug("Closing server"); + try + { + this.server.close(); + } + catch (IOException e) + { + logger.error("Could not close server", e); + process.descendants().forEach(ProcessHandle::destroy); + process.destroy(); + } + } + } + + /** + * Utility method for launching a browser instance + * + * @throws IOException - Thrown if there is a problem launching the browser. + */ + private static Process launchBrowser() throws IOException + { + Process process = null; + String os = System.getProperty("os.name").toLowerCase(); + String arch = System.getProperty("os.arch").toLowerCase(); + String args; + File browserDir = new File(System.getProperty("user.dir"), "firefox"); + + args = " -profile \"" + System.getProperty("user.dir") + "/profile\""; + logger.info("Running on {} {}", os, arch); +// System.getProperties().entrySet().stream().forEach(entry -> logger.info("{}: {}", entry.getKey(), entry.getValue())); + if (os.contains("windows")) + { + process = Runtime.getRuntime().exec(new File(browserDir, "firefox.exe").getAbsolutePath() + args); + } + else if (os.contains("linux")) + { + process = Runtime.getRuntime().exec(new File(browserDir, "firefox").getAbsolutePath() + args); + } + if (process == null) + { + throw new IOException("Could not find Firefox installation for OS " + os + " " + arch); + } + + return process; + } + + public static void notifyInstance() + { + instanceWaiter.notify(); + } + + public static void notifyAllInstance() + { + synchronized (instanceWaiter) + { + instanceWaiter.notifyAll(); + } + } + + public static void waitInstance() throws InterruptedException + { + synchronized (instanceWaiter) + { + instanceWaiter.wait(); + } + } + + public static void waitInstance(long timeoutMillis) throws InterruptedException + { + synchronized (instanceWaiter) + { + instanceWaiter.wait(timeoutMillis); + } + } + + public static void waitInstance(long timeoutMillis, int nanos) throws InterruptedException + { + synchronized (instanceWaiter) + { + instanceWaiter.wait(timeoutMillis, nanos); + } + } +} diff --git a/build.gradle b/build.gradle index e6448f7..8935686 100644 --- a/build.gradle +++ b/build.gradle @@ -9,11 +9,4 @@ * For more details take a look at the Java Quickstart chapter in the Gradle * user guide available at https://docs.gradle.org/4.4.1/userguide/tutorial_java_projects.html */ -ext.defaultPackage = "edu.regis.universeplayer" - -task bundleAddOn(type: Zip) -{ - archiveName = "universalmusic@regis.edu.xpi" - destinationDir = file("./browser/profile/extensions") - from (files("./add-on")) -} \ No newline at end of file +ext.defaultPackage = "edu.regis.universeplayer" \ No newline at end of file diff --git a/interface/build.gradle b/interface/build.gradle index 46d42ad..dc12c2f 100644 --- a/interface/build.gradle +++ b/interface/build.gradle @@ -31,6 +31,7 @@ dependencies { implementation 'uk.co.caprica:vlcj:4.7.1' implementation project(":libwave") implementation project(":browserCommands") + implementation project(":browser") // 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 @@ -46,5 +47,4 @@ targetCompatibility = JavaVersion.VERSION_16 mainClassName = defaultPackage + '.player.Interface' //mainClassName = defaultPackage + '.localPlayer.Player' -compileJava.dependsOn rootProject.bundleAddOn compileJava.dependsOn ':addonInter:installAddon' \ No newline at end of file diff --git a/interface/src/main/java/edu/regis/universeplayer/browser/Browser.java b/interface/src/main/java/edu/regis/universeplayer/browser/BrowserPlayer.java similarity index 55% rename from interface/src/main/java/edu/regis/universeplayer/browser/Browser.java rename to interface/src/main/java/edu/regis/universeplayer/browser/BrowserPlayer.java index 881fa3c..066ab62 100644 --- a/interface/src/main/java/edu/regis/universeplayer/browser/Browser.java +++ b/interface/src/main/java/edu/regis/universeplayer/browser/BrowserPlayer.java @@ -15,11 +15,9 @@ import edu.regis.universeplayer.data.Song; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; -import java.net.*; +import java.net.URL; import java.util.LinkedList; -import java.util.Scanner; import java.util.concurrent.*; /** @@ -28,172 +26,35 @@ import java.util.concurrent.*; * @author William Hubbard * @since 0.1 */ -public class Browser extends MessageRunner implements Player +public class BrowserPlayer implements Player, UpdateListener { - private static final Logger logger = LoggerFactory.getLogger(Browser.class); - private final Process process; - private final ServerSocket server; - private final Socket socket; + private static final Logger logger = LoggerFactory.getLogger(BrowserPlayer.class); private final LinkedList listeners = new LinkedList<>(); private InternetSong currentSong; - private boolean running = true; - public static Browser createBrowser() throws IOException, InterruptedException + private Browser browserRef = null; + + private Browser getBrowser() { - ServerSocket server = new ServerSocket(BrowserConstants.PORT, 50, InetAddress.getByName(null)); - logger.debug("Server started."); - - int startExit; - Browser browser; - Process browserProcess = launchBrowser(); - /* - * Wait for the browser to fully start. - */ - startExit = browserProcess.waitFor(); - if (startExit != 0) + if (browserRef == null) { - logger.error("Error in browser launch (exit code {})", startExit); - try (Scanner scanner = new Scanner(browserProcess.getErrorStream())) + while (Browser.getInstance() == null) { - while (scanner.hasNextLine()) + try { - logger.error(scanner.nextLine()); + Browser.waitInstance(); + } + catch (InterruptedException e) + { + logger.error("Interrupted while waiting for browser to initialize.", e); } } - throw new IOException("Error in browser launch (exit code " + startExit + ")"); + Browser.getInstance().addUpdateListener(this); + browserRef = Browser.getInstance(); } - logger.debug("Browser started."); - - ConnectException connErr = null; - logger.debug("Attempting connection"); - Socket socket = server.accept(); - if (!socket.isBound()) - { - logger.error("Socket not bound"); - } - else if (!socket.isConnected()) - { - logger.error("Socket not connected"); - } - else if (socket.isClosed()) - { - logger.error("Socket prematurely closed"); - } - else if (socket.isInputShutdown()) - { - logger.error("Socket input prematurely closed."); - } - else if (socket.isOutputShutdown()) - { - logger.error("Socket input prematurely closed."); - } - else - { - logger.debug("Connection established."); - } - browser = new Browser(socket, server, browserProcess); - return browser; - } - - private Browser(Socket socket, ServerSocket server, Process process) throws IOException - { - super("BrowserRunner", socket.getInputStream(), socket.getOutputStream()); - this.socket = socket; - this.server = server; - 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 !this.running; - } - - @Override - protected void onClose() - { - logger.debug("Closing socket"); - try - { - this.socket.close(); - } - catch (IOException e) - { - logger.error("Could not close socket", e); - } - finally - { - logger.debug("Closing server"); - try - { - this.server.close(); - } - catch (IOException e) - { - logger.error("Could not close server", e); - } - } - } - - /** - * Utility method for launching a browser instance - * - * @throws IOException - Thrown if there is a problem launching the browser. - */ - private static Process launchBrowser() throws IOException - { - Process process = null; - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch").toLowerCase(); - String args; - File browserDir = new File(System.getProperty("user.dir"), "browser"); - - if (!browserDir.exists()) - { - browserDir = new File(System.getProperty("user.dir"), "../browser"); - } - args = " -profile \"" + browserDir.getAbsolutePath() + "/profile\""; - logger.info("Running on {} {}", os, arch); -// System.getProperties().entrySet().stream().forEach(entry -> logger.info("{}: {}", entry.getKey(), entry.getValue())); - if (os.contains("windows")) - { - if (arch.contains("64")) - { - logger.debug("Starting Windows x86_64 browser"); - process = Runtime.getRuntime().exec(new File(browserDir, "windows64/firefox.exe").getAbsolutePath() + args); - } - else - { - logger.debug("Starting Windows x86 browser"); - process = Runtime.getRuntime().exec(new File(browserDir, "windows32/firefox.exe").getAbsolutePath() + args); - } - } - else if (os.contains("linux")) - { - if (arch.contains("64")) - { - logger.debug("Starting Linux x86_64 browser"); - process = Runtime.getRuntime().exec(new File(browserDir, "linux64/firefox").getAbsolutePath() + args); - } - else - { - logger.debug("Starting Linux 86 browser"); - process = Runtime.getRuntime().exec(new File(browserDir, "linux32/firefox").getAbsolutePath() + args); - } - } - if (process == null) - { - throw new IOException("Could not find Firefox installation for OS " + os + " " + arch); - } - - return process; + return browserRef; } @Override @@ -207,7 +68,7 @@ public class Browser extends MessageRunner implements Player { try { - return new ForwardedFuture(this.sendObject(new CommandLoadSong(song.location))); + return new ForwardedFuture(getBrowser().sendObject(new CommandLoadSong(song.location))); } catch (IOException e) { @@ -222,33 +83,18 @@ public class Browser extends MessageRunner implements Player @Override public QueryFuture close() { - this.running = false; try { - QueryFuture future = new ForwardedFuture(this.sendObject(new CommandQuit())); + QueryFuture future = new ForwardedFuture(getBrowser().sendObject(new CommandQuit())); return future; } catch (IOException 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(); - } + logger.error("Could not send message", e); + return null; } - return null; } - + /** * Adds a listener for playback status updates. * @@ -259,7 +105,7 @@ public class Browser extends MessageRunner implements Player { this.listeners.add(listener); } - + /** * Checks to see if a listener has been added. * @@ -271,7 +117,7 @@ public class Browser extends MessageRunner implements Player { return this.listeners.contains(listener); } - + /** * Removes a listener for playback status updates. * @@ -283,24 +129,12 @@ public class Browser extends MessageRunner implements Player this.listeners.remove(listener); } - @Override - protected void triggerUpdateListeners(Object ob) - { - PlaybackEvent status; - super.triggerUpdateListeners(ob); - if (ob instanceof PlaybackInfo) - { - status = new PlaybackEvent(this, (PlaybackInfo) ob); - this.listeners.forEach(l -> l.onPlaybackChanged(status)); - } - } - @Override public QueryFuture play() { try { - return new ForwardedFuture(this.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY))); + return new ForwardedFuture(getBrowser().sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY))); } catch (IOException e) { @@ -314,7 +148,7 @@ public class Browser extends MessageRunner implements Player { try { - return new ForwardedFuture(this.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); + return new ForwardedFuture(getBrowser().sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); } catch (IOException e) { @@ -328,7 +162,7 @@ public class Browser extends MessageRunner implements Player { try { - return new ForwardedFuture(this.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); + return new ForwardedFuture(getBrowser().sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); } catch (IOException e) { @@ -336,7 +170,7 @@ public class Browser extends MessageRunner implements Player return null; } } - + /** * Stops playback of the current song. */ @@ -345,7 +179,7 @@ public class Browser extends MessageRunner implements Player { try { - return new ForwardedFuture(this.sendObject(new CommandLoadSong((URL) null))); + return new ForwardedFuture(getBrowser().sendObject(new CommandLoadSong((URL) null))); } catch (IOException e) { @@ -353,13 +187,13 @@ public class Browser extends MessageRunner implements Player return null; } } - + @Override public QueryFuture seek(float time) { try { - return new ForwardedFuture(this.sendObject(new CommandSeek(time))); + return new ForwardedFuture(getBrowser().sendObject(new CommandSeek(time))); } catch (IOException e) { @@ -367,7 +201,7 @@ public class Browser extends MessageRunner implements Player return null; } } - + /** * Obtains the player's current playback status. * @@ -378,56 +212,57 @@ public class Browser extends MessageRunner implements Player { try { - Future future = this.sendObject(new QueryStatus()); - return new QueryFuture<>() { + Future future = getBrowser().sendObject(new QueryStatus()); + return new QueryFuture<>() + { private CommandReturn getVal() throws ExecutionException, InterruptedException { return ((CommandReturn) future.get()); } - + private CommandReturn getVal(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { return ((CommandReturn) 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 { String value = getVal().getReturnValue(); return PlaybackStatus.valueOf(value); } - + @Override public PlaybackStatus get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { @@ -455,6 +290,17 @@ public class Browser extends MessageRunner implements Player return null; } + @Override + public void onUpdate(Object object, MessageRunner runner) + { + PlaybackEvent status; + if (object instanceof PlaybackInfo) + { + status = new PlaybackEvent(this, (PlaybackInfo) object); + this.listeners.forEach(l -> l.onPlaybackChanged(status)); + } + } + private class ForwardedFuture implements QueryFuture { private final Future future; diff --git a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java index e30cbdf..ea1022f 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java @@ -6,6 +6,7 @@ package edu.regis.universeplayer.player; import edu.regis.universeplayer.Player; import edu.regis.universeplayer.browser.Browser; +import edu.regis.universeplayer.browser.BrowserPlayer; import edu.regis.universeplayer.data.InternetSong; import edu.regis.universeplayer.data.Queue; import edu.regis.universeplayer.data.*; @@ -74,6 +75,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL Thread browserThread; Interface inter = null; Browser browser; + BrowserPlayer browserPlayer; try { /* @@ -82,6 +84,11 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL * otherwise. */ logger.info("Starting application"); + + browser = Browser.createBrowser(); + browserThread = new Thread(browser); + browserThread.start(); + inter = new Interface(); inter.setSize(700, 500); SongProvider.INSTANCE.addUpdateListener(inter); @@ -91,13 +98,11 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL try { - inter.players.add(browser = Browser.createBrowser()); - browserThread = new Thread(browser); - browserThread.start(); + inter.players.add(browserPlayer = new BrowserPlayer()); logger.debug("Sending ping"); browser.sendObject("ping"); - Runtime.getRuntime().addShutdownHook(new Thread(browser::close)); - Player.REGISTERED_PLAYERS.put(InternetSong.class, browser); + Runtime.getRuntime().addShutdownHook(new Thread(browserPlayer::close)); + Player.REGISTERED_PLAYERS.put(InternetSong.class, browserPlayer); // LinkedList> pingRequests = new LinkedList<>(); // for (int i = 0; i < 20; i++) diff --git a/settings.gradle b/settings.gradle index fd5acb4..033fe10 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,5 +23,6 @@ rootProject.name = 'UniversalMusicPlayer' include ':interface' include ':add-on' include ':addonInter' +include ':browser' include ':browserCommands' include ':libwave'