Adds full support for YouTube playback.
While a few optimizations could be made, YouTube playback is almost as seamless as local playback.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer;
|
||||
|
||||
import edu.regis.universeplayer.PlaybackStatus;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.EventObject;
|
||||
|
||||
public class PlaybackInfo implements Serializable
|
||||
{
|
||||
private final Song currentSong;
|
||||
/**
|
||||
* The time we are currently at in the song, in seconds.
|
||||
*/
|
||||
private final float playTime;
|
||||
|
||||
/**
|
||||
* The current status of the player.
|
||||
*/
|
||||
private final PlaybackStatus status;
|
||||
|
||||
public PlaybackInfo(Song song, float playTime, PlaybackStatus status)
|
||||
{
|
||||
this.currentSong = song;
|
||||
this.playTime = playTime;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the song currently playing.
|
||||
*
|
||||
* @return The current song, or null if none is loaded.
|
||||
*/
|
||||
public Song getSong()
|
||||
{
|
||||
return this.currentSong;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the current play time.
|
||||
*
|
||||
* @return The play time of the player. This will be zero if the song is
|
||||
* stopped or not loaded.
|
||||
*/
|
||||
public float getPlayTime()
|
||||
{
|
||||
return this.playTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the current status of the player.
|
||||
*
|
||||
* @return - The player's playback status.
|
||||
*/
|
||||
public PlaybackStatus getStatus()
|
||||
{
|
||||
return this.status;
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -33,11 +30,12 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
private final ExecutorService executor;
|
||||
private final LinkedList<MessageListener> listeners = new LinkedList<>();
|
||||
protected final HashMap<Integer, Future<Object>> messageResponses = new HashMap<>();
|
||||
protected final Queue<Object> updates = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Creates a message handler.
|
||||
*
|
||||
* @param name - The name of the handler. This is used in logging.
|
||||
* @param name - The name of the handler. This is used in logging.
|
||||
* @param input - The input from our external source.
|
||||
* @param output - The output to the external source.
|
||||
*/
|
||||
@@ -142,6 +140,29 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
toRemove.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Send in any updates
|
||||
*/
|
||||
synchronized (this.updates)
|
||||
{
|
||||
if (this.updates.size() > 0)
|
||||
{
|
||||
Object update;
|
||||
while ((update = this.updates.poll()) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
messageByte = serializeObject(update);
|
||||
writeMessage(browserOut, -1, messageByte);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not send response message for " + update, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
@@ -231,6 +252,19 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an object through the handler as an update not associated with any message.
|
||||
*
|
||||
* @param object - The object to send.
|
||||
*/
|
||||
public void sendUpdate(Object object)
|
||||
{
|
||||
synchronized (this.updates)
|
||||
{
|
||||
this.updates.add(object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a listener has been added to the list.
|
||||
*
|
||||
|
||||
@@ -39,6 +39,8 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
protected final HashMap<Integer, MessagePacket> sentQueue = new HashMap<>();
|
||||
protected int messagesSent = 0;
|
||||
|
||||
protected final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Creates a message runner.
|
||||
*
|
||||
@@ -77,6 +79,7 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
BufferedOutputStream browserOut = null;
|
||||
MessagePacket packet;
|
||||
|
||||
int messageNum = -1;
|
||||
byte[][] returnMessage;
|
||||
ByteBuffer numBuffer = ByteBuffer.allocate(4);
|
||||
|
||||
@@ -98,18 +101,19 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
{
|
||||
try
|
||||
{
|
||||
packet.returnValue.index = this.messagesSent;
|
||||
writeMessage(browserOut, this.messagesSent, packet.message);
|
||||
messageNum = this.messagesSent;
|
||||
packet.returnValue.index = messageNum;
|
||||
writeMessage(browserOut, messageNum, packet.message);
|
||||
|
||||
synchronized (this.sentQueue)
|
||||
{
|
||||
this.sentQueue.put(this.messagesSent, packet);
|
||||
this.sentQueue.put(messageNum, packet);
|
||||
this.messagesSent++;
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not send message " + this.messagesSent + " " + new String(packet.message, StandardCharsets.UTF_8), e);
|
||||
logger.error("Could not send message " + messageNum + " " + new String(packet.message, StandardCharsets.UTF_8), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,35 +122,44 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
*/
|
||||
try
|
||||
{
|
||||
if (!this.sentQueue.isEmpty())
|
||||
if (browserIn.available() > 0)
|
||||
{
|
||||
if (browserIn.available() > 0)
|
||||
logger.trace("Reading message");
|
||||
/*
|
||||
* Wait for a response from the browser.
|
||||
*/
|
||||
returnMessage = this.readMessage(browserIn);
|
||||
if (returnMessage == null)
|
||||
{
|
||||
/*
|
||||
* Wait for a response from the browser.
|
||||
*/
|
||||
returnMessage = this.readMessage(browserIn);
|
||||
if (returnMessage == null)
|
||||
logger.info("Connection closed.");
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
numBuffer.clear();
|
||||
numBuffer.put(returnMessage[0]);
|
||||
numBuffer.clear();
|
||||
messageNum = numBuffer.getInt();
|
||||
if (messageNum == -1)
|
||||
{
|
||||
logger.info("Connection closed.");
|
||||
break;
|
||||
/*
|
||||
* Not a response, just a generic update.
|
||||
*/
|
||||
Object ob = deserializeObject(returnMessage[1]);
|
||||
this.triggerUpdateListeners(ob);
|
||||
logger.debug("Received update {}", ob);
|
||||
}
|
||||
else
|
||||
{
|
||||
numBuffer.clear();
|
||||
numBuffer.put(returnMessage[0]);
|
||||
numBuffer.clear();
|
||||
packet = this.sentQueue.get(numBuffer.getInt());
|
||||
packet = this.sentQueue.get(messageNum);
|
||||
if (packet == null)
|
||||
{
|
||||
numBuffer.clear();
|
||||
logger.warn("Received message {} for nonexistant packet", numBuffer.getInt());
|
||||
logger.warn("Received message {} for nonexistant packet", messageNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.returnMessage = returnMessage[1];
|
||||
numBuffer.clear();
|
||||
logger.debug("Reading message {} {}", numBuffer.getInt(), new String(packet.returnMessage, StandardCharsets.UTF_8));
|
||||
logger.debug("Reading message {} {}", messageNum, new String(packet.returnMessage, StandardCharsets.UTF_8));
|
||||
synchronized (this.readLock)
|
||||
{
|
||||
logger.trace("Received message {}, notifying futures.", packet.returnValue.index);
|
||||
@@ -236,6 +249,48 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for when an update not associated with a message comes
|
||||
* through the runner.
|
||||
*
|
||||
* @param listener - The listener to add.
|
||||
*/
|
||||
public void addUpdateListener(UpdateListener listener)
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an update listener is present.
|
||||
*
|
||||
* @param listener - The listener to check for.
|
||||
* @return Whether the provided listener is present.
|
||||
*/
|
||||
public boolean hasUpdateListener(UpdateListener listener)
|
||||
{
|
||||
return this.listeners.contains(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener for when an update not associated with a message comes
|
||||
* through the runner.
|
||||
*
|
||||
* @param listener - The listener to remove.
|
||||
*/
|
||||
public void removeUpdateListener(UpdateListener listener)
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers update listeners
|
||||
* @param ob - The update that came through.
|
||||
*/
|
||||
protected void triggerUpdateListeners(Object ob)
|
||||
{
|
||||
this.listeners.forEach(l -> l.onUpdate(ob, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* This represents a sent message in storage.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* An update listener is called when an update is sent through the message
|
||||
* runner that is not associated with a message.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public interface UpdateListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* Called when an update is received from a message runner.
|
||||
*
|
||||
* @param object - The received object
|
||||
* @param runner - The runner that received the object.
|
||||
*/
|
||||
void onUpdate(Object object, MessageRunner runner);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
public class Album implements Comparable<Album>
|
||||
{
|
||||
public int id;
|
||||
public String name;
|
||||
public String[] artists;
|
||||
public transient ImageIcon art;
|
||||
public int year;
|
||||
public String[] genres;
|
||||
public int totalTracks;
|
||||
public int totalDiscs;
|
||||
|
||||
@Override
|
||||
public int compareTo(Album o)
|
||||
{
|
||||
if (o != null)
|
||||
{
|
||||
return this.name.compareTo(o.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class InternetSong extends Song
|
||||
{
|
||||
public URL location;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* This song represents a song found on the local file system.
|
||||
*/
|
||||
public class LocalSong extends Song
|
||||
{
|
||||
public File file;
|
||||
public String type;
|
||||
public String codec;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Contains data for a song.
|
||||
*/
|
||||
public class Song implements Comparable<Song>, Serializable
|
||||
{
|
||||
public String title;
|
||||
public String[] artists;
|
||||
public int trackNum;
|
||||
public int disc;
|
||||
public long duration;
|
||||
|
||||
/**
|
||||
* A reference to the album this song is part of.
|
||||
*/
|
||||
public Album album;
|
||||
|
||||
@Override
|
||||
public int compareTo(Song o)
|
||||
{
|
||||
if (o != null)
|
||||
{
|
||||
int comp = this.album != null ? this.album.compareTo(o.album) : o.album != null ? 1 : 0;
|
||||
if (comp == 0)
|
||||
{
|
||||
comp = Integer.compare(this.disc, o.disc);
|
||||
if (comp == 0)
|
||||
{
|
||||
comp = Integer.compare(this.trackNum, o.trackNum);
|
||||
if (comp == 0)
|
||||
{
|
||||
comp = this.title != null ? this.title.compareTo(o.title) : o.title != null ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user