Adds some integration with firefox.

This is still rather unstable, but it runs.
This commit is contained in:
Markil3
2021-07-11 23:33:39 -06:00
parent 2bc005f031
commit 704ab06f85
535 changed files with 2381 additions and 23 deletions

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/
package edu.regis.universe_player;
import edu.regis.universe_player.data.Song;
/**
* This interface serves as the connection to a music player of some sort, whether it be
* browser-based or from a file.
*
* @param <T> - The type of songs supported.
* @author William Hubbard
* @since 0.1
*/
public interface Player<T extends Song>
{
/**
* Obtains the song currently playing.
*
* @return The current song, or null if none is playing.
*/
Song getCurrentSong();
/**
* Loads up a song
*
* @param song - The song to load.
*/
void play(T song);
/**
* Enables playback of the current song, if one is active.
*/
void play();
/**
* Pauses playback of the current song.
*/
void pause();
/**
* Toggles between playing and pausing the current song.
*/
void togglePlayback();
/**
* Sets the current song time to the specified position.
*
* @param time - The specified time in the song, in seconds.
*/
void seek(float time);
/**
* Checks to see if the song is paused.
*
* @return Whether or not the song is paused.
*/
boolean isPaused();
/**
* Obtains the time we are currently at in the current song.
*
* @return - The current song position in seconds, or -1 if no song is playing.
*/
float getCurrentTime();
/**
* Gets the length of the current song.
*
* @return The song length, in seconds.
*/
float getLength();
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/
package edu.regis.universe_player.browser;
import com.google.gson.Gson;
import com.google.gson.stream.JsonWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
/**
* This serves as a central point for controlling the browser process.
*
* @author William Hubbard
* @since 0.1
*/
public class Browser
{
private static Process process;
/**
* Utility method for launching a browser instance
*
* @throws IOException
*/
public static void launchBrowser() throws IOException
{
String os = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch").toLowerCase();
String args = " -profile browser/profile";
if (process != null && process.isAlive())
{
process.destroy();
}
if (os.contains("windows"))
{
setupWindows();
process = Runtime.getRuntime().exec("browser/windows/FirefoxPortable.exe" + args);
}
else if (os.contains("linux"))
{
setupLinux();
if (arch.contains("64"))
{
process = Runtime.getRuntime().exec("./browser/linux64/firefox" + args);
}
else
{
process = Runtime.getRuntime().exec("./browser/linux32/firefox" + args);
}
}
System.out.println(os);
}
/**
* Performs first-time setup for Windows applications.
*/
private static void setupWindows()
{
try
{
Runtime.getRuntime()
.exec("REG ADD HKEY_CURRENT_USER\\SOFTWARE\\Mozilla\\NativeMessagingHosts /v universal_music /d \"" + System
.getProperty("user.dir") + "\\bin\\interface.bat\" ");
}
catch (IOException e)
{
System.err.println("Could not set Windows registry key.");
e.printStackTrace();
}
}
/**
* Performs first-time setup for OSX applications.
*/
private static void setupMac()
{
Gson gson = new Gson();
try (JsonWriter writer = gson.newJsonWriter(new FileWriter(new File(System
.getProperty("user.home"), "Library/Application Support/Mozilla/NativeMessagingHosts/universal_music.json"))))
{
writeManifest(writer, System.getProperty("user.dir") + "/bin/interface");
}
catch (IOException e)
{
System.err.println("Could not set Mac application manifest.");
e.printStackTrace();
}
}
/**
* Performs first-time setup for Linux applications.
*/
private static void setupLinux()
{
Gson gson = new Gson();
try (JsonWriter writer = gson.newJsonWriter(new FileWriter(new File(System
.getProperty("user.home"), ".mozilla/native-messaging-hosts/universal_music.json"))))
{
writeManifest(writer, System.getProperty("user.dir") + "/bin/interface");
}
catch (IOException e)
{
System.err.println("Could not set Linux application manifest.");
e.printStackTrace();
}
}
private static void writeManifest(JsonWriter writer, String path) throws IOException
{
writer.beginObject();
writer.name("name");
writer.value("universal_music");
writer.name("description");
writer.value("Universal Music Player");
writer.name("path");
writer.value(path);
writer.name("type");
writer.value("stdio");
writer.name("allowed_extensions");
writer.beginArray();
writer.value("universal_music@regis.edu");
writer.endArray();
writer.endObject();
}
/**
* Tells the browser process to shut down.
*/
public static void closeBrowser()
{
if (process != null && process.isAlive())
{
process.destroy();
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/
package edu.regis.universe_player.browser;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Reader;
import java.util.Scanner;
/**
* This class will send messages to the
*/
public class MessageManager implements Closeable
{
private final JsonReader in;
private final JsonWriter out;
private final Gson gson;
public MessageManager() throws IOException
{
this.gson = new Gson();
this.in = this.gson.newJsonReader(new InputStreamReader(System.in));
this.out = this.gson.newJsonWriter(new OutputStreamWriter(System.out));
}
/**
* Retrieves an object from the browser.
*
* @return - A parsed object. What it is depends on the browser.
*/
public Object getMessage() throws IOException
{
Object value = null;
this.in.beginObject();
while (this.in.hasNext())
{
switch (this.in.nextName())
{
case "response":
switch (this.in.peek())
{
case STRING:
value = this.in.nextString();
break;
case NUMBER:
value = this.in.nextDouble();
break;
case BOOLEAN:
value = this.in.nextBoolean();
break;
case NULL:
this.in.nextNull();
break;
default:
throw new IOException("Unexpected JSON type" + this.in.peek());
}
break;
}
}
this.in.endObject();
return value;
}
/**
* Sends a string message to the browser.
*
* @param message - The message to send.
*/
public void writeMessage(String message)
{
String value = null;
String type = null;
try
{
this.out.beginObject();
this.out.name("message");
this.out.value(message);
this.out.endObject();
}
catch (IOException e)
{
}
}
/**
* Pings the browser.
*/
public void ping()
{
try
{
this.out.beginObject();
this.out.name("command");
this.out.value("ping");
this.out.endObject();
}
catch (IOException e)
{
}
}
@Override
public void close() throws IOException
{
this.in.close();
this.out.close();
}
}

View File

@@ -8,11 +8,17 @@ import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.util.Collection;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import edu.regis.universe_player.browser.Browser;
import edu.regis.universe_player.browser.MessageManager;
import edu.regis.universe_player.data.CollectionType;
import edu.regis.universe_player.data.Song;
@@ -22,7 +28,7 @@ import edu.regis.universe_player.data.Song;
* @author William Hubbard
* @version 0.1
*/
public class Interface extends JFrame implements SongDisplayListener, ComponentListener
public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener
{
/**
* A reference to the panel containing links to different collection views.
@@ -41,9 +47,32 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
*/
private JScrollPane centerView;
/**
* A link to the browser.
*/
private MessageManager browser;
public static void main(String[] args)
{
try
{
Browser.launchBrowser();
}
catch (IOException e)
{
e.printStackTrace();
JOptionPane.showMessageDialog(null, e, "Could not open browser background", JOptionPane.ERROR_MESSAGE);
}
Interface inter = new Interface();
try
{
inter.browser = new MessageManager();
}
catch (IOException e)
{
e.printStackTrace();
JOptionPane.showMessageDialog(null, e, "Could not open browser communication", JOptionPane.ERROR_MESSAGE);
}
inter.pack();
inter.setVisible(true);
}
@@ -54,12 +83,16 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
public Interface()
{
super();
PlayerControls controls;
this.setTitle("Universal Music Player");
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane()
.add(this.collectionTypes = new Collections(), BorderLayout.LINE_START);
this.collectionTypes.addSongDisplayListener(this);
this.getContentPane().add(new PlayerControls(), BorderLayout.PAGE_END);
controls = new PlayerControls();
controls.addCommandListener(this);
this.getContentPane().add(controls, BorderLayout.PAGE_END);
this.songList = new SongList();
this.collectionList = new CollectionList();
@@ -70,6 +103,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
this.componentResized(null);
this.addComponentListener(this);
this.addWindowListener(this);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@@ -130,4 +164,91 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
{
}
@Override
public void windowOpened(WindowEvent windowEvent)
{
}
@Override
public void windowClosing(WindowEvent windowEvent)
{
Browser.closeBrowser();
if (this.browser != null)
{
try
{
this.browser.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
@Override
public void windowClosed(WindowEvent windowEvent)
{
}
@Override
public void windowIconified(WindowEvent windowEvent)
{
}
@Override
public void windowDeiconified(WindowEvent windowEvent)
{
}
@Override
public void windowActivated(WindowEvent windowEvent)
{
}
@Override
public void windowDeactivated(WindowEvent windowEvent)
{
}
/**
* Called when a playback command is issued.
*
* @param command - The command issued.
* @param data - Additional data relevent to the command.
*/
@Override
public void onCommand(PlaybackCommand command, Object data)
{
Object message = null;
if (this.browser != null)
{
synchronized (this.browser)
{
try
{
this.browser.ping();
message = this.browser.getMessage();
}
catch (IOException e)
{
e.printStackTrace();
JOptionPane.showMessageDialog(this, e, "Could not send message to browser", JOptionPane.ERROR_MESSAGE);
}
}
}
if (message != null)
{
if ("ping".equals(message))
{
JOptionPane.showMessageDialog(this, "Ping received!");
}
}
}
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/
package edu.regis.universe_player.player;
/**
* Contains possible playback commands that the play can send.
*
* @author William Hubbard
* @version 0.1
*/
public enum PlaybackCommand
{
PLAY, PAUSE, NEXT, PREVIOUS, SEEK
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/
package edu.regis.universe_player.player;
import java.util.EventListener;
/**
* A listener that allows objects to observe when playback commands are triggered.
*
* @author William Hubbard
* @version 0.1
*/
public interface PlaybackCommandListener extends EventListener
{
/**
* Called when a playback command is issued.
*
* @param command - The command issued.
* @param data - Additional data relevent to the command.
*/
void onCommand(PlaybackCommand command, Object data);
}

View File

@@ -6,6 +6,7 @@ package edu.regis.universe_player.player;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.LinkedList;
import javax.swing.ImageIcon;
import javax.swing.JButton;
@@ -26,12 +27,16 @@ public class PlayerControls extends JPanel
private JButton prevButton;
private JSlider progress;
/**
* A list of all things interested in knowing when we trigger a command.
*/
private LinkedList<PlaybackCommandListener> listeners = new LinkedList<>();
public PlayerControls()
{
final Dimension BUTTON_SIZE = new Dimension(32, 32);
final Dimension ICON_SIZE = new Dimension(16, 16);
ImageIcon icon;
JButton button;
JPanel buttonCont, progressCont;
FlowLayout buttonLayout;
SpringLayout progressLayout;
@@ -43,36 +48,45 @@ public class PlayerControls extends JPanel
buttonCont = new JPanel(buttonLayout);
this.add(buttonCont);
button = new JButton();
this.prevButton = new JButton();
icon = new ImageIcon(this.getClass()
.getResource("/gui/icons/skipPrev.png"), "Previous Button");
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
button.setIcon(icon);
button.setPreferredSize(BUTTON_SIZE);
buttonCont.add(button);
this.prevButton = button;
this.prevButton.setIcon(icon);
this.prevButton.setPreferredSize(BUTTON_SIZE);
this.prevButton.addActionListener(actionEvent -> {
this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null);
});
buttonCont.add(this.prevButton);
button = new JButton();
this.playButton = new JButton();
icon = new ImageIcon(this.getClass().getResource("/gui/icons/play.png"), "Play Button");
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
button.setIcon(icon);
button.setPreferredSize(BUTTON_SIZE);
buttonCont.add(button);
this.playButton = button;
this.playButton.setIcon(icon);
this.playButton.setPreferredSize(BUTTON_SIZE);
this.playButton.addActionListener(actionEvent -> {
this.triggerCommandListeners(PlaybackCommand.PLAY, null);
});
buttonCont.add(this.playButton);
button = new JButton();
this.nextButton = new JButton();
icon = new ImageIcon(this.getClass().getResource("/gui/icons/skipNext.png"), "Next Button");
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
button.setIcon(icon);
button.setPreferredSize(BUTTON_SIZE);
buttonCont.add(button);
this.nextButton = button;
this.nextButton.setIcon(icon);
this.nextButton.setPreferredSize(BUTTON_SIZE);
this.nextButton.addActionListener(actionEvent -> {
this.triggerCommandListeners(PlaybackCommand.NEXT, null);
});
buttonCont.add(this.nextButton);
progressLayout = new SpringLayout();
progressCont = new JPanel(progressLayout);
this.add(progressCont);
this.progress = new JSlider();
this.progress.addChangeListener(changeEvent -> {
this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue());
});
this.add(this.progress);
layout.putConstraint(SpringLayout.NORTH, buttonCont, 0, SpringLayout.NORTH, this);
@@ -83,4 +97,38 @@ public class PlayerControls extends JPanel
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.progress);
layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this);
}
/**
* Adds a listener for playback commands.
*
* @param listener - The listener to add.
*/
public void addCommandListener(PlaybackCommandListener listener)
{
this.listeners.add(listener);
}
/**
* Removes a playback listener.
*
* @param listener - The listener to remove.
*/
public void removeCommandListener(PlaybackCommandListener listener)
{
this.listeners.remove(listener);
}
/**
* Triggers all the command listeners.
*
* @param command - The command to trigger.
* @param data - Extra command data.
*/
protected void triggerCommandListeners(PlaybackCommand command, Object data)
{
for (PlaybackCommandListener listener : this.listeners)
{
listener.onCommand(command, data);
}
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB