The Big Fat RoboDJ revamp. Rewrote the server using Presents; rewrote the
chooser to interface using distributed objects; implemented 90% of the music importing process (just need to upload the data to the server and stuff the new music records in the database). git-svn-id: https://samskivert.googlecode.com/svn/trunk@1489 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
<property name="build.compiler" value="jikes"/>
|
||||
<property name="web.home" value="/export/samskivert/pages/code"/>
|
||||
<property name="install.dir" value="${web.home}/${app.name}"/>
|
||||
<property name="narya.home" value="../../work/narya"/>
|
||||
|
||||
<!-- we want to access the environment -->
|
||||
<property environment="env"/>
|
||||
@@ -21,22 +22,51 @@
|
||||
<property name="javadoc.dir" value="${deploy.dir}/docs"/>
|
||||
<property name="doc.packages" value="robodj.*"/>
|
||||
<property name="doc.overview" value="robodj/overview.html"/>
|
||||
<property name="copyright.holder" value="Michael Bayne"/>
|
||||
<property name="nartool.path" value="${narya.home}/bin"/>
|
||||
<property name="classes.dir" value="${deploy.dir}/classes"/>
|
||||
|
||||
<!-- declare our classpath -->
|
||||
<path id="classpath">
|
||||
<fileset dir="${java.libraries}" includesfile="lib/CLIENT_LIBS"/>
|
||||
<fileset dir="lib" includes="**/*.jar"/>
|
||||
<pathelement location="${deploy.dir}/classes"/>
|
||||
<pathelement location="${classes.dir}"/>
|
||||
</path>
|
||||
|
||||
<!-- generates .java files for all .dobj files -->
|
||||
<target name="gendobj">
|
||||
<apply executable="${nartool.path}/gendobj"
|
||||
failonerror="true" parallel="true">
|
||||
<srcfile/>
|
||||
<fileset dir="src/java" includes="**/*.dobj"/>
|
||||
</apply>
|
||||
</target>
|
||||
|
||||
<!-- generates marshaller and dispatcher classes for all invocation -->
|
||||
<!-- service declarations -->
|
||||
<target name="genservice">
|
||||
<!-- make sure the service class files are all compiled -->
|
||||
<javac srcdir="src/java" destdir="${classes.dir}"
|
||||
debug="on" optimize="${build.optimize}" deprecation="on">
|
||||
<classpath refid="classpath"/>
|
||||
<include name="**/*Service.java"/>
|
||||
</javac>
|
||||
<apply executable="${nartool.path}/genservice"
|
||||
failonerror="true" parallel="true">
|
||||
<arg value="--classpath"/>
|
||||
<arg value="${classes.dir}:${java.libraries}/narya-distrib.jar:${java.libraries}/narya-base.jar"/>
|
||||
<arg value="--sourcedir"/>
|
||||
<arg value="src/java"/>
|
||||
<fileset dir="src/java" includes="**/*Service.java"/>
|
||||
</apply>
|
||||
</target>
|
||||
|
||||
<!-- prepares the application directories -->
|
||||
<target name="prepare">
|
||||
<target name="prepare" depends="gendobj">
|
||||
<mkdir dir="${deploy.dir}"/>
|
||||
<mkdir dir="${deploy.dir}/classes"/>
|
||||
<mkdir dir="${classes.dir}"/>
|
||||
<mkdir dir="${javadoc.dir}"/>
|
||||
<!-- copy media and properties into the target directory -->
|
||||
<copy todir="${deploy.dir}/classes">
|
||||
<copy todir="${classes.dir}">
|
||||
<fileset dir="${src.dir}" includes="**/*.jpg"/>
|
||||
<fileset dir="${src.dir}" includes="**/*.png"/>
|
||||
<fileset dir="${src.dir}" includes="**/*.properties"/>
|
||||
@@ -55,7 +85,7 @@
|
||||
|
||||
<!-- build the java class files -->
|
||||
<target name="compile" depends="prepare">
|
||||
<javac srcdir="${src.dir}" destdir="${deploy.dir}/classes"
|
||||
<javac srcdir="${src.dir}" destdir="${classes.dir}"
|
||||
debug="on" optimize="off" deprecation="off">
|
||||
<classpath refid="classpath"/>
|
||||
</javac>
|
||||
@@ -68,7 +98,6 @@
|
||||
windowtitle="${app.name} API"
|
||||
doctitle="${app.name} API"
|
||||
overview="${src.dir}/${doc.overview}"
|
||||
bottom="Copyright © 2002 ${copyright.holder}. All Rights Reserved."
|
||||
destdir="${javadoc.dir}">
|
||||
<classpath refid="classpath"/>
|
||||
<link href="http://www.waywardgeeks.org/code/samskivert/javadoc"/>
|
||||
@@ -82,7 +111,7 @@
|
||||
<!-- builds our distribution files (war and jar) -->
|
||||
<target name="dist" depends="prepare,compile">
|
||||
<jar destfile="${deploy.dir}/${dist.jar}"
|
||||
basedir="${deploy.dir}/classes"/>
|
||||
basedir="${classes.dir}"/>
|
||||
</target>
|
||||
|
||||
<!-- installs the JWS app -->
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
@@ -18,7 +19,16 @@ import com.samskivert.util.ConfigUtil;
|
||||
import com.samskivert.util.PropertiesUtil;
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.ClientObserver;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.net.UsernamePasswordCreds;
|
||||
|
||||
import robodj.Log;
|
||||
import robodj.client.DJService;
|
||||
import robodj.data.DJObject;
|
||||
import robodj.repository.Model;
|
||||
import robodj.repository.Repository;
|
||||
import robodj.util.ErrorUtil;
|
||||
@@ -31,16 +41,19 @@ import robodj.util.ServerControl;
|
||||
* collection and managing the playlist.
|
||||
*/
|
||||
public class Chooser
|
||||
implements Client.Invoker, ClientObserver, Subscriber
|
||||
{
|
||||
public static Repository repository;
|
||||
|
||||
public static Model model;
|
||||
|
||||
public static ServerControl scontrol;
|
||||
public static Client client;
|
||||
|
||||
public static DJObject djobj;
|
||||
|
||||
public static ChooserFrame frame;
|
||||
|
||||
public static void main (String[] args)
|
||||
public void init ()
|
||||
{
|
||||
boolean error = false;
|
||||
|
||||
@@ -85,8 +98,11 @@ public class Chooser
|
||||
"No music server host specified in configuration.");
|
||||
}
|
||||
// establish a connection with the music server
|
||||
scontrol = new ServerControl(
|
||||
host, RDJPrefs.getMusicDaemonPort());
|
||||
client = new Client(new UsernamePasswordCreds(), this);
|
||||
client.addClientObserver(this);
|
||||
client.setServer(host, Client.DEFAULT_SERVER_PORT);
|
||||
client.logon();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
String errmsg = "Unable to communicate with music " +
|
||||
"server on host '" + RDJPrefs.getMusicDaemonHost() + "' ";
|
||||
@@ -98,12 +114,110 @@ public class Chooser
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// create our primary user interface frame, center the frame in
|
||||
// the screen and show it
|
||||
frame = new ChooserFrame();
|
||||
frame.setSize(660, frame.getToolkit().getScreenSize().height - 60);
|
||||
SwingUtil.centerWindow(frame);
|
||||
frame.setVisible(true);
|
||||
// documentation inherited from interface
|
||||
public void clientDidLogon (Client lclient)
|
||||
{
|
||||
// subscribe to our DJObject
|
||||
DJService djsvc = (DJService)client.requireService(DJService.class);
|
||||
djsvc.getDJOid(client, new DJService.ResultListener() {
|
||||
public void requestProcessed (Object result) {
|
||||
int djoid = ((Integer)result).intValue();
|
||||
client.getDObjectManager().subscribeToObject(
|
||||
djoid, Chooser.this);
|
||||
}
|
||||
public void requestFailed (String cause) {
|
||||
ErrorUtil.reportError("Failed to fetch DJ oid: " + cause, null);
|
||||
System.exit(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientObjectDidChange (Client client)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientDidLogoff (Client client)
|
||||
{
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientFailedToLogon (Client lclient, Exception cause)
|
||||
{
|
||||
if (ErrorUtil.reportError("Failed to connect to server", cause)) {
|
||||
System.exit(-1);
|
||||
} else {
|
||||
Thread t = new Thread() {
|
||||
public void run () {
|
||||
RDJPrefsPanel.display(true);
|
||||
client.setServer(RDJPrefs.getMusicDaemonHost(),
|
||||
Client.DEFAULT_SERVER_PORT);
|
||||
client.logon();
|
||||
}
|
||||
};
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void clientConnectionFailed (Client client, Exception cause)
|
||||
{
|
||||
ErrorUtil.reportError("Lost connection to server", cause);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean clientWillLogoff (Client client)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void invokeLater (Runnable run)
|
||||
{
|
||||
SwingUtilities.invokeLater(run);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void objectAvailable (DObject object)
|
||||
{
|
||||
djobj = (DJObject)object;
|
||||
displayInterface();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void requestFailed (int oid, ObjectAccessException cause)
|
||||
{
|
||||
ErrorUtil.reportError("Failed to fetch DJ object", cause);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
protected void displayInterface ()
|
||||
{
|
||||
if (frame == null) {
|
||||
// create our primary user interface frame, center the frame
|
||||
// in the screen and show it
|
||||
frame = new ChooserFrame();
|
||||
frame.setSize(660, frame.getToolkit().getScreenSize().height - 60);
|
||||
SwingUtil.centerWindow(frame);
|
||||
frame.setVisible(true);
|
||||
} else {
|
||||
frame.toFront();
|
||||
}
|
||||
}
|
||||
|
||||
public static void exit ()
|
||||
{
|
||||
client.logoff(false);
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
Chooser chooser = new Chooser();
|
||||
chooser.init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
package robodj.chooser;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import com.samskivert.swing.Controller;
|
||||
import com.samskivert.swing.util.TaskMaster;
|
||||
import com.samskivert.swing.util.TaskObserver;
|
||||
|
||||
import robodj.Log;
|
||||
|
||||
@@ -16,12 +13,9 @@ import robodj.Log;
|
||||
* Handles top-level chooser UI commands.
|
||||
*/
|
||||
public class ChooserController extends Controller
|
||||
implements TaskObserver
|
||||
{
|
||||
public ChooserController ()
|
||||
{
|
||||
// read our playing state
|
||||
TaskMaster.invokeMethodTask("refreshPlaying", Chooser.scontrol, this);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@@ -29,48 +23,28 @@ public class ChooserController extends Controller
|
||||
{
|
||||
String cmd = event.getActionCommand();
|
||||
if (cmd.equals("exit")) {
|
||||
System.exit(0);
|
||||
Chooser.exit();
|
||||
|
||||
} else if (cmd.equals("skip")) {
|
||||
TaskMaster.invokeMethodTask("skip", Chooser.scontrol, this);
|
||||
Chooser.djobj.skip();
|
||||
|
||||
} else if (cmd.equals("back")) {
|
||||
TaskMaster.invokeMethodTask("back", Chooser.scontrol, this);
|
||||
Chooser.djobj.back();
|
||||
|
||||
} else if (cmd.equals("pause")) {
|
||||
TaskMaster.invokeMethodTask("pause", Chooser.scontrol, this);
|
||||
Chooser.djobj.pause();
|
||||
|
||||
} else if (cmd.equals("play")) {
|
||||
TaskMaster.invokeMethodTask("play", Chooser.scontrol, this);
|
||||
Chooser.djobj.play();
|
||||
|
||||
} else if (cmd.equals("stop")) {
|
||||
TaskMaster.invokeMethodTask("stop", Chooser.scontrol, this);
|
||||
Chooser.djobj.stop();
|
||||
|
||||
} else {
|
||||
System.out.println("Unknown action event: " + cmd);
|
||||
Log.warning("Unknown action event: " + cmd);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void taskCompleted (String name, Object result)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void taskFailed (String name, Throwable exception)
|
||||
{
|
||||
String msg;
|
||||
if (Exception.class.equals(exception.getClass())) {
|
||||
msg = exception.getMessage();
|
||||
} else {
|
||||
msg = exception.toString();
|
||||
}
|
||||
JOptionPane.showMessageDialog(Chooser.frame, msg, "Error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
Log.logStackTrace(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ package robodj.chooser;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -19,17 +21,21 @@ import robodj.repository.*;
|
||||
import robodj.util.ButtonUtil;
|
||||
import robodj.util.FancyPanel;
|
||||
import robodj.util.RDJPrefs;
|
||||
import robodj.util.ServerControl.PlayingListener;
|
||||
|
||||
public class ChooserFrame extends JFrame
|
||||
implements PlayingListener, ControllerProvider
|
||||
implements ControllerProvider
|
||||
{
|
||||
public ChooserFrame ()
|
||||
{
|
||||
super("RoboDJ Chooser " + Version.RELEASE_VERSION);
|
||||
|
||||
// quit if we're closed
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
// quit gracefully if we're closed
|
||||
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
||||
addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing (WindowEvent e) {
|
||||
Chooser.exit();
|
||||
}
|
||||
});
|
||||
|
||||
// we create a top-level panel to manage everything
|
||||
GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH);
|
||||
@@ -109,8 +115,8 @@ public class ChooserFrame extends JFrame
|
||||
}
|
||||
});
|
||||
|
||||
// add ourselves as a playing listener
|
||||
Chooser.scontrol.addPlayingListener(this);
|
||||
// TODO: add ourselves as a playing listener
|
||||
// Chooser.djobj.addListener(this);
|
||||
|
||||
// create our controller
|
||||
_controller = new ChooserController();
|
||||
@@ -152,6 +158,7 @@ public class ChooserFrame extends JFrame
|
||||
return user;
|
||||
}
|
||||
|
||||
// TODO: convert to dobj biz
|
||||
public void playingUpdated (int songid, boolean paused)
|
||||
{
|
||||
if (songid == -1 || paused) {
|
||||
|
||||
@@ -16,8 +16,10 @@ import javax.swing.event.AncestorListener;
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.samskivert.swing.util.TaskMaster;
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
|
||||
import robodj.Log;
|
||||
import robodj.data.PlaylistEntry;
|
||||
import robodj.repository.Entry;
|
||||
import robodj.repository.Song;
|
||||
|
||||
@@ -71,11 +73,7 @@ public abstract class EntryController extends ItemController
|
||||
_list.populateEntries(_entries);
|
||||
|
||||
} else if (name.equals("readAndPlay")) {
|
||||
for (int i = 0; i < _entry.songs.length; i++) {
|
||||
Chooser.scontrol.append(_entry.songs[i].entryid,
|
||||
_entry.songs[i].songid,
|
||||
_entry.songs[i].location);
|
||||
}
|
||||
appendToPlaylist(_entry.songs);
|
||||
|
||||
} else if (name.equals("readSongs")) {
|
||||
_list.populateSong(_entry);
|
||||
@@ -110,7 +108,7 @@ public abstract class EntryController extends ItemController
|
||||
|
||||
} else if (cmd.equals(SongItem.PLAY)) {
|
||||
Song song = SongItem.getSong(e.getSource());
|
||||
Chooser.scontrol.append(song.entryid, song.songid, song.location);
|
||||
appendToPlaylist(new Song[] { song });
|
||||
|
||||
} else if (cmd.equals("refresh")) {
|
||||
// re-read the category
|
||||
@@ -164,6 +162,20 @@ public abstract class EntryController extends ItemController
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
protected void appendToPlaylist (Song[] songs)
|
||||
{
|
||||
PlaylistEntry[] plist = Chooser.djobj.playlist;
|
||||
if (plist == null) {
|
||||
plist = new PlaylistEntry[0];
|
||||
}
|
||||
for (int i = 0; i < songs.length; i++) {
|
||||
PlaylistEntry entry = new PlaylistEntry(
|
||||
songs[i].entryid, songs[i].songid, songs[i].title);
|
||||
plist = (PlaylistEntry[])ArrayUtil.append(plist, entry);
|
||||
}
|
||||
Chooser.djobj.setPlaylist(plist);
|
||||
}
|
||||
|
||||
protected EntryList _list;
|
||||
protected Entry[] _entries;
|
||||
protected Entry _entry;
|
||||
|
||||
@@ -13,32 +13,42 @@ import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.samskivert.swing.util.TaskAdapter;
|
||||
import com.samskivert.swing.util.TaskMaster;
|
||||
|
||||
import com.threerings.presents.dobj.AttributeChangeListener;
|
||||
import com.threerings.presents.dobj.AttributeChangedEvent;
|
||||
|
||||
import robodj.Log;
|
||||
import robodj.chooser.PlaylistPanel.PlaylistEntry;
|
||||
import robodj.data.DJObject;
|
||||
import robodj.data.PlaylistEntry;
|
||||
import robodj.repository.Entry;
|
||||
import robodj.repository.Song;
|
||||
import robodj.util.ServerControl.PlayingListener;
|
||||
|
||||
/**
|
||||
* Handles playlist UI commands.
|
||||
*/
|
||||
public class PlaylistController extends ItemController
|
||||
implements PlayingListener
|
||||
implements AttributeChangeListener
|
||||
{
|
||||
public PlaylistController (PlaylistPanel panel)
|
||||
{
|
||||
super(panel);
|
||||
_panel = panel;
|
||||
|
||||
// add ourselves as a playing listener
|
||||
Chooser.scontrol.addPlayingListener(this);
|
||||
// listen to the DJ object
|
||||
Chooser.djobj.addListener(this);
|
||||
}
|
||||
|
||||
public void wasAdded ()
|
||||
{
|
||||
super.wasAdded();
|
||||
|
||||
// start up the task that reads the CD info from the database
|
||||
TaskMaster.invokeMethodTask("readPlaylist", this, this);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@@ -46,121 +56,47 @@ public class PlaylistController extends ItemController
|
||||
{
|
||||
String cmd = e.getActionCommand();
|
||||
if (cmd.equals(PlaylistPanel.CLEAR)) {
|
||||
TaskMaster.invokeMethodTask("clear", Chooser.scontrol, this);
|
||||
|
||||
} else if (cmd.equals(PlaylistPanel.REFRESH)) {
|
||||
_panel.prepareForRefresh();
|
||||
|
||||
// start up the task that reads the CD info from the database
|
||||
TaskMaster.invokeMethodTask("readPlaylist", this, this);
|
||||
Chooser.djobj.clear();
|
||||
|
||||
} else if (cmd.equals(SongItem.SKIP_TO)) {
|
||||
JButton button = (JButton)e.getSource();
|
||||
SongItem item = (SongItem)button.getParent();
|
||||
|
||||
// fire up a task to talk to the music daemon
|
||||
final int songid = item.getSong().songid;
|
||||
TaskMaster.invokeTask("noop", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
Chooser.scontrol.skipto(songid);
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
Chooser.djobj.play(((Integer)item.extra).intValue());
|
||||
|
||||
} else if (cmd.equals(SongItem.REMOVE)) {
|
||||
JButton button = (JButton)e.getSource();
|
||||
SongItem item = (SongItem)button.getParent();
|
||||
|
||||
// remove the entry UI elements
|
||||
item.getParent().remove(item);
|
||||
SwingUtil.refresh(_panel); // relayout
|
||||
|
||||
// remove the entry from the playlist
|
||||
_panel.plist.remove((PlaylistEntry)item.extra);
|
||||
|
||||
// fire up a task to talk to the music daemon
|
||||
final int songid = item.getSong().songid;
|
||||
TaskMaster.invokeTask("noop", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
Chooser.scontrol.remove(songid);
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
int sidx = ((Integer)item.extra).intValue();
|
||||
Chooser.djobj.remove(sidx, 1);
|
||||
|
||||
} else if (cmd.equals(PlaylistPanel.REMOVE_ALL)) {
|
||||
JButton src = (JButton)e.getSource();
|
||||
PlaylistEntry entry =
|
||||
(PlaylistEntry)src.getClientProperty("entry");
|
||||
PlaylistPanel.PLE entry =
|
||||
(PlaylistPanel.PLE)src.getClientProperty("entry");
|
||||
|
||||
// remove all entries starting with this one until we get to
|
||||
// one that has a different entryid
|
||||
int count = 0;
|
||||
Iterator iter = _panel.plist.iterator();
|
||||
while (iter.hasNext()) {
|
||||
PlaylistEntry pe = (PlaylistEntry)iter.next();
|
||||
int start = -1, length = 1;
|
||||
for (int ii = 0; ii < _panel.plist.size(); ii++) {
|
||||
PlaylistPanel.PLE pe = (PlaylistPanel.PLE)_panel.plist.get(ii);
|
||||
if (entry == pe) {
|
||||
count++;
|
||||
// remove the entry UI elements
|
||||
pe.item.getParent().remove(pe.item);
|
||||
// remove the entry
|
||||
iter.remove();
|
||||
|
||||
} else if (count > 0) {
|
||||
start = ii;
|
||||
} else if (start != -1) {
|
||||
if (pe.entry.entryid == entry.entry.entryid) {
|
||||
count++;
|
||||
// remove the entry UI elements
|
||||
pe.item.getParent().remove(pe.item);
|
||||
// remove the entry
|
||||
iter.remove();
|
||||
|
||||
length++;
|
||||
} else {
|
||||
// we hit an entry that doesn't match, bail
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove the title and stuff
|
||||
JPanel tpanel = (JPanel)src.getParent();
|
||||
tpanel.getParent().remove(tpanel);
|
||||
|
||||
_panel.revalidate(); // relayout
|
||||
|
||||
// fire up a task to talk to the music daemon, telling it to
|
||||
// remove those entries
|
||||
final int songid = entry.song.songid;
|
||||
final int fcount = count;
|
||||
TaskMaster.invokeTask("noop", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
Chooser.scontrol.removeGroup(songid, fcount);
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
if (start != -1) {
|
||||
Chooser.djobj.remove(start, length);
|
||||
}
|
||||
|
||||
} else if (cmd.equals(PlaylistPanel.SHUFFLE)) {
|
||||
// put all of the songs in the playlist into a list and
|
||||
// shuffle them
|
||||
ArrayList slist = new ArrayList();
|
||||
for (Iterator iter = _panel.plist.iterator(); iter.hasNext(); ) {
|
||||
slist.add(((PlaylistEntry)iter.next()).song);
|
||||
}
|
||||
final Song[] songs = (Song[])slist.toArray(new Song[slist.size()]);
|
||||
ArrayUtil.shuffle(songs);
|
||||
|
||||
// now clear and reload the playlist entirely
|
||||
TaskMaster.invokeTask("noop", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
Chooser.scontrol.clear();
|
||||
for (int ii = 0; ii < songs.length; ii++) {
|
||||
Chooser.scontrol.append(
|
||||
songs[ii].entryid, songs[ii].songid,
|
||||
songs[ii].location);
|
||||
}
|
||||
// finally queue up a UI refresh
|
||||
postAction(_panel, PlaylistPanel.REFRESH);
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
// shuffle!
|
||||
Chooser.djobj.shuffle();
|
||||
|
||||
} else {
|
||||
return super.handleAction(e);
|
||||
@@ -169,86 +105,70 @@ public class PlaylistController extends ItemController
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void attributeChanged (AttributeChangedEvent event)
|
||||
{
|
||||
if (event.getName().equals(DJObject.PLAYLIST)) {
|
||||
TaskMaster.invokeMethodTask("readPlaylist", this, this);
|
||||
|
||||
} else if (event.getName().equals(DJObject.PLAYING)) {
|
||||
playingUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
public void readPlaylist ()
|
||||
throws PersistenceException
|
||||
{
|
||||
// clear out any previous playlist
|
||||
_panel.plist.clear();
|
||||
|
||||
// get the playlist from the music daemon
|
||||
String[] plist = Chooser.scontrol.getPlaylist();
|
||||
|
||||
// parse it into playlist entries
|
||||
for (int i = 0; i < plist.length; i++) {
|
||||
String[] toks = StringUtil.split(plist[i], "\t");
|
||||
int eid, sid;
|
||||
try {
|
||||
eid = Integer.parseInt(toks[0]);
|
||||
sid = Integer.parseInt(toks[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.warning("Bogus playlist record: '" + plist[i] + "'.");
|
||||
continue;
|
||||
}
|
||||
Entry entry = Chooser.model.getEntry(eid);
|
||||
int pcount = (Chooser.djobj.playlist == null) ? 0 :
|
||||
Chooser.djobj.playlist.length;
|
||||
for (int ii = 0; ii < pcount; ii++) {
|
||||
PlaylistEntry pentry = Chooser.djobj.playlist[ii];
|
||||
Entry entry = Chooser.model.getEntry(pentry.entryId);
|
||||
if (entry != null) {
|
||||
Song song = entry.getSong(sid);
|
||||
Song song = entry.getSong(pentry.songId);
|
||||
if (song != null) {
|
||||
_panel.plist.add(new PlaylistEntry(entry, song));
|
||||
_panel.plist.add(new PlaylistPanel.PLE(entry, song));
|
||||
} else {
|
||||
Log.warning("No song for entry? [sid=" + sid +
|
||||
", entry=" + entry + "].");
|
||||
Log.warning("No song for entry? [pentry=" + pentry + "].");
|
||||
}
|
||||
} else {
|
||||
Log.warning("Unable to load entry [eid=" + eid + "].");
|
||||
Log.warning("Unable to load entry? [pentry=" + pentry + "].");
|
||||
}
|
||||
}
|
||||
|
||||
// now find out what's currently playing
|
||||
Chooser.scontrol.refreshPlaying();
|
||||
}
|
||||
|
||||
public void playingUpdated (int songid, boolean paused)
|
||||
public void playingUpdated ()
|
||||
{
|
||||
// unhighlight whoever is playing now
|
||||
PlaylistEntry pentry = getPlayingEntry();
|
||||
if (pentry != null && pentry.item != null) {
|
||||
pentry.item.setIsPlaying(false);
|
||||
if (_playing != null) {
|
||||
_playing.item.setIsPlaying(false);
|
||||
_playing = null;
|
||||
}
|
||||
|
||||
// grab the new playing song id
|
||||
_playid = songid;
|
||||
int pidx = Chooser.djobj.playing;
|
||||
if (pidx >= 0 && _panel.plist.size() > pidx) {
|
||||
_playing = (PlaylistPanel.PLE)_panel.plist.get(pidx);
|
||||
}
|
||||
|
||||
// highlight the playing song
|
||||
for (int i = 0; i < _panel.plist.size(); i++) {
|
||||
PlaylistEntry entry = (PlaylistEntry)_panel.plist.get(i);
|
||||
if (entry.song.songid == _playid && entry.item != null) {
|
||||
entry.item.setIsPlaying(true);
|
||||
}
|
||||
if (_playing != null) {
|
||||
_playing.item.setIsPlaying(true);
|
||||
}
|
||||
_panel.repaint();
|
||||
}
|
||||
|
||||
protected PlaylistEntry getPlayingEntry ()
|
||||
{
|
||||
for (int i = 0; i < _panel.plist.size(); i++) {
|
||||
PlaylistEntry entry = (PlaylistEntry)_panel.plist.get(i);
|
||||
if (entry.song.songid == _playid) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void taskCompleted (String name, Object result)
|
||||
{
|
||||
if (name.equals("readPlaylist")) {
|
||||
_panel.populatePlaylist(_panel.plist, _playid);
|
||||
_panel.populatePlaylist(_panel.plist);
|
||||
playingUpdated();
|
||||
} else {
|
||||
super.taskCompleted(name, result);
|
||||
}
|
||||
}
|
||||
|
||||
protected PlaylistPanel _panel;
|
||||
|
||||
protected int _playid;
|
||||
protected PlaylistPanel.PLE _playing;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import robodj.util.ButtonUtil;
|
||||
|
||||
public class PlaylistPanel extends ControlledPanel
|
||||
{
|
||||
public static class PlaylistEntry
|
||||
public static class PLE
|
||||
{
|
||||
public Entry entry;
|
||||
|
||||
@@ -30,7 +30,7 @@ public class PlaylistPanel extends ControlledPanel
|
||||
|
||||
public SongItem item;
|
||||
|
||||
public PlaylistEntry (Entry entry, Song song)
|
||||
public PLE (Entry entry, Song song)
|
||||
{
|
||||
this.entry = entry;
|
||||
this.song = song;
|
||||
@@ -42,8 +42,6 @@ public class PlaylistPanel extends ControlledPanel
|
||||
}
|
||||
}
|
||||
|
||||
public static final String REFRESH = "refresh";
|
||||
|
||||
public static final String CLEAR = "clear";
|
||||
|
||||
public static final String REMOVE_ALL = "remove_all";
|
||||
@@ -80,8 +78,6 @@ public class PlaylistPanel extends ControlledPanel
|
||||
_clearbut = ButtonUtil.createControlButton(
|
||||
CLEAR_TIP, CLEAR, CLEAR_ICON_PATH);
|
||||
cbar.add(_clearbut);
|
||||
cbar.add(ButtonUtil.createControlButton(
|
||||
REFRESH_TIP, REFRESH, REFRESH_ICON_PATH));
|
||||
cbar.add(ButtonUtil.createControlButton(
|
||||
SHUFFLE_TIP, SHUFFLE, SHUFFLE_ICON_PATH));
|
||||
add(cbar, GroupLayout.FIXED);
|
||||
@@ -97,8 +93,6 @@ public class PlaylistPanel extends ControlledPanel
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
// load up the playlist
|
||||
_controller.postAction(this, REFRESH);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@@ -118,7 +112,7 @@ public class PlaylistPanel extends ControlledPanel
|
||||
_bpanel.revalidate();
|
||||
}
|
||||
|
||||
protected void populatePlaylist (ArrayList plist, int playid)
|
||||
protected void populatePlaylist (ArrayList plist)
|
||||
{
|
||||
// clear out any existing children
|
||||
_bpanel.removeAll();
|
||||
@@ -128,10 +122,12 @@ public class PlaylistPanel extends ControlledPanel
|
||||
gl.setOffAxisPolicy(GroupLayout.EQUALIZE);
|
||||
gl.setOffAxisJustification(GroupLayout.LEFT);
|
||||
|
||||
int current = Chooser.djobj.playing;
|
||||
|
||||
// add buttons for every entry
|
||||
String title = null;
|
||||
for (int i = 0; i < plist.size(); i++) {
|
||||
PlaylistEntry entry = (PlaylistEntry)plist.get(i);
|
||||
for (int ii = 0; ii < plist.size(); ii++) {
|
||||
PLE entry = (PLE)plist.get(ii);
|
||||
JButton button;
|
||||
|
||||
// add record/artist indicators when the record and artist
|
||||
@@ -155,13 +151,13 @@ public class PlaylistPanel extends ControlledPanel
|
||||
|
||||
// create a song item to display this playlist entry
|
||||
entry.item = new SongItem(entry.song, SongItem.PLAYLIST);
|
||||
entry.item.setIsPlaying(entry.song.songid == playid);
|
||||
entry.item.extra = entry;
|
||||
entry.item.setIsPlaying(current == ii);
|
||||
entry.item.extra = new Integer(ii);
|
||||
_bpanel.add(entry.item);
|
||||
|
||||
// let the bpanel know that we want to scroll the active track
|
||||
// label into place once we're all laid out
|
||||
if (entry.song.songid == playid) {
|
||||
if (current == ii) {
|
||||
_bpanel.setScrollTarget(entry.item);
|
||||
}
|
||||
}
|
||||
@@ -229,7 +225,6 @@ public class PlaylistPanel extends ControlledPanel
|
||||
|
||||
// icon paths
|
||||
protected static final String ICON_ROOT = "/robodj/chooser/images/";
|
||||
protected static final String REFRESH_ICON_PATH = ICON_ROOT + "refresh.png";
|
||||
protected static final String CLEAR_ICON_PATH = ICON_ROOT + "clear.png";
|
||||
protected static final String SHUFFLE_ICON_PATH = ICON_ROOT + "shuffle.png";
|
||||
protected static final String SKIPTO_ICON_PATH = ICON_ROOT + "skip.png";
|
||||
@@ -239,7 +234,6 @@ public class PlaylistPanel extends ControlledPanel
|
||||
ICON_ROOT + "remove_song.png";
|
||||
|
||||
// button tips
|
||||
protected static final String REFRESH_TIP = "Refresh the playlist";
|
||||
protected static final String CLEAR_TIP =
|
||||
"Clear all songs from the playlist";
|
||||
protected static final String SHUFFLE_TIP =
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* Defines the main RoboDJ services.
|
||||
*/
|
||||
public interface DJService extends InvocationService
|
||||
{
|
||||
/** Used to obtain the oid of the DJ object. */
|
||||
public void getDJOid (Client client, ResultListener listener);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.data;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService.ResultListener;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
import com.threerings.presents.dobj.InvocationResponseEvent;
|
||||
import robodj.client.DJService;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link DJService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
public class DJMarshaller extends InvocationMarshaller
|
||||
implements DJService
|
||||
{
|
||||
/** The method id used to dispatch {@link #getDJOid} requests. */
|
||||
public static final int GET_DJOID = 1;
|
||||
|
||||
// documentation inherited from interface
|
||||
public void getDJOid (Client arg1, ResultListener arg2)
|
||||
{
|
||||
ResultMarshaller listener2 = new ResultMarshaller();
|
||||
listener2.listener = arg2;
|
||||
sendRequest(arg1, GET_DJOID, new Object[] {
|
||||
listener2
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.data;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.ListUtil;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
import robodj.Log;
|
||||
|
||||
/**
|
||||
* Contains the shared state of RoboDJ.
|
||||
*/
|
||||
public class DJObject extends DObject
|
||||
{
|
||||
/** The current playlist. */
|
||||
public PlaylistEntry[] playlist;
|
||||
|
||||
/** The index of the currently playing track. */
|
||||
public int playing = -1;
|
||||
|
||||
/**
|
||||
* Returns the currently playing track.
|
||||
*/
|
||||
public PlaylistEntry getPlaying ()
|
||||
{
|
||||
return (playing < 0) ? null : getEntry(playing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the playlist entry for the next song in the playlist.
|
||||
*/
|
||||
public PlaylistEntry getNext ()
|
||||
{
|
||||
return getEntry(playing + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the playlist entry with the current index or null if the
|
||||
* index is not valid.
|
||||
*/
|
||||
public PlaylistEntry getEntry (int index)
|
||||
{
|
||||
return havePlaylist() ? playlist[index % playlist.length] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the specified track in the current playlist.
|
||||
*/
|
||||
public void play (int index)
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
setPlaying(index % playlist.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void play ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
// TODO: handle unpausing
|
||||
if (playing == -1) {
|
||||
setPlaying(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void skip ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
play(playing + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void back ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
play(playing + playlist.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop ()
|
||||
{
|
||||
setPlaying(-1);
|
||||
}
|
||||
|
||||
public void pause ()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void remove (int start, int length)
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
setPlaylist((PlaylistEntry[])ArrayUtil.splice(
|
||||
playlist, start, length));
|
||||
if (playing >= (start+length)) {
|
||||
setPlaying(playing-length);
|
||||
} else if (playing > start) {
|
||||
setPlaying(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
PlaylistEntry entry = getPlaying();
|
||||
ArrayUtil.shuffle(playlist);
|
||||
setPlaylist(playlist);
|
||||
setPlaying(ListUtil.indexOf(playlist, entry));
|
||||
}
|
||||
}
|
||||
|
||||
public void clear ()
|
||||
{
|
||||
setPlaying(-1);
|
||||
setPlaylist(null);
|
||||
}
|
||||
|
||||
/** Helper function. */
|
||||
protected boolean havePlaylist ()
|
||||
{
|
||||
return (playlist != null && playlist.length > 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.data;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.ListUtil;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
import robodj.Log;
|
||||
|
||||
/**
|
||||
* Contains the shared state of RoboDJ.
|
||||
*/
|
||||
public class DJObject extends DObject
|
||||
{
|
||||
/** The field name of the <code>playlist</code> field. */
|
||||
public static final String PLAYLIST = "playlist";
|
||||
|
||||
/** The field name of the <code>playing</code> field. */
|
||||
public static final String PLAYING = "playing";
|
||||
|
||||
/** The current playlist. */
|
||||
public PlaylistEntry[] playlist;
|
||||
|
||||
/** The index of the currently playing track. */
|
||||
public int playing = -1;
|
||||
|
||||
/**
|
||||
* Returns the currently playing track.
|
||||
*/
|
||||
public PlaylistEntry getPlaying ()
|
||||
{
|
||||
return (playing < 0) ? null : getEntry(playing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the playlist entry for the next song in the playlist.
|
||||
*/
|
||||
public PlaylistEntry getNext ()
|
||||
{
|
||||
return getEntry(playing + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the playlist entry with the current index or null if the
|
||||
* index is not valid.
|
||||
*/
|
||||
public PlaylistEntry getEntry (int index)
|
||||
{
|
||||
return havePlaylist() ? playlist[index % playlist.length] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the specified track in the current playlist.
|
||||
*/
|
||||
public void play (int index)
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
setPlaying(index % playlist.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void play ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
// TODO: handle unpausing
|
||||
if (playing == -1) {
|
||||
setPlaying(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void skip ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
play(playing + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void back ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
play(playing + playlist.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop ()
|
||||
{
|
||||
setPlaying(-1);
|
||||
}
|
||||
|
||||
public void pause ()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void remove (int start, int length)
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
setPlaylist((PlaylistEntry[])ArrayUtil.splice(
|
||||
playlist, start, length));
|
||||
if (playing >= (start+length)) {
|
||||
setPlaying(playing-length);
|
||||
} else if (playing > start) {
|
||||
setPlaying(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle ()
|
||||
{
|
||||
if (havePlaylist()) {
|
||||
PlaylistEntry entry = getPlaying();
|
||||
ArrayUtil.shuffle(playlist);
|
||||
setPlaylist(playlist);
|
||||
setPlaying(ListUtil.indexOf(playlist, entry));
|
||||
}
|
||||
}
|
||||
|
||||
public void clear ()
|
||||
{
|
||||
setPlaying(-1);
|
||||
setPlaylist(null);
|
||||
}
|
||||
|
||||
/** Helper function. */
|
||||
protected boolean havePlaylist ()
|
||||
{
|
||||
return (playlist != null && playlist.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the <code>playlist</code> field be set to the specified
|
||||
* value. The local value will be updated immediately and an event
|
||||
* will be propagated through the system to notify all listeners that
|
||||
* the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
public void setPlaylist (PlaylistEntry[] playlist)
|
||||
{
|
||||
requestAttributeChange(PLAYLIST, playlist);
|
||||
this.playlist = playlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the <code>index</code>th element of
|
||||
* <code>playlist</code> field be set to the specified value. The local
|
||||
* value will be updated immediately and an event will be propagated
|
||||
* through the system to notify all listeners that the attribute did
|
||||
* change. Proxied copies of this object (on clients) will apply the
|
||||
* value change when they received the attribute changed notification.
|
||||
*/
|
||||
public void setPlaylistAt (PlaylistEntry value, int index)
|
||||
{
|
||||
requestElementUpdate(PLAYLIST, value, index);
|
||||
this.playlist[index] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the <code>playing</code> field be set to the specified
|
||||
* value. The local value will be updated immediately and an event
|
||||
* will be propagated through the system to notify all listeners that
|
||||
* the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
public void setPlaying (int playing)
|
||||
{
|
||||
requestAttributeChange(PLAYING, new Integer(playing));
|
||||
this.playing = playing;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.data;
|
||||
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
import robodj.repository.Entry;
|
||||
import robodj.repository.Song;
|
||||
|
||||
/**
|
||||
* Defines an entry currently in the playlist.
|
||||
*/
|
||||
public class PlaylistEntry extends SimpleStreamableObject
|
||||
implements DSet.Entry
|
||||
{
|
||||
/** The album with which we are associated. */
|
||||
public int entryId;
|
||||
|
||||
/** The id of the song represented by this entry. */
|
||||
public int songId;
|
||||
|
||||
/** The title of this entry (duplicated from the Song to simplify
|
||||
* playlist display. */
|
||||
public String title;
|
||||
|
||||
/** Zero argument constructor for streaming. */
|
||||
public PlaylistEntry ()
|
||||
{
|
||||
}
|
||||
|
||||
/** Creates a playlist entry from the specified entry and song. */
|
||||
public PlaylistEntry (Entry entry, Song song)
|
||||
{
|
||||
this(entry.entryid, song.songid, song.title);
|
||||
}
|
||||
|
||||
/** Creates a playlist entry from the specified information. */
|
||||
public PlaylistEntry (int entryId, int songId, String title)
|
||||
{
|
||||
this.entryId = entryId;
|
||||
this.songId = songId;
|
||||
this.title = title;
|
||||
_key = entryId + ":" + songId;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Comparable getKey ()
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean equals (Object other)
|
||||
{
|
||||
if (other instanceof PlaylistEntry) {
|
||||
return _key.equals(((PlaylistEntry)other)._key);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int hashCode ()
|
||||
{
|
||||
return _key.hashCode();
|
||||
}
|
||||
|
||||
protected String _key;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.importer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import robodj.repository.Entry;
|
||||
import robodj.repository.Song;
|
||||
|
||||
/**
|
||||
* Displays an Entry and a list of similar entries and a check box.
|
||||
*/
|
||||
public class EntryUploader extends JPanel
|
||||
{
|
||||
public EntryUploader (Entry entry, ArrayList similar)
|
||||
{
|
||||
super(new BorderLayout(5, 5));
|
||||
add(new JLabel(entry.artist + " - " + entry.title),
|
||||
BorderLayout.CENTER);
|
||||
add(_checked = new JCheckBox(), BorderLayout.WEST);
|
||||
StringBuffer extra = new StringBuffer("<html>");
|
||||
for (int ii = 0; ii < entry.songs.length; ii++) {
|
||||
Song song = entry.songs[ii];
|
||||
extra.append(song.position).append(". ");
|
||||
extra.append(song.title).append("<br>");
|
||||
}
|
||||
if (similar.size() > 0) {
|
||||
extra.append("<p><u>Similar entries:</u><br>");
|
||||
for (Iterator iter = similar.iterator(); iter.hasNext(); ) {
|
||||
Entry siment = (Entry)iter.next();
|
||||
extra.append(siment.artist).append(" - ").append(siment.title);
|
||||
extra.append("<br>");
|
||||
}
|
||||
}
|
||||
extra.append("</html>");
|
||||
JLabel exlab = new JLabel(extra.toString());
|
||||
exlab.setFont(getFont().deriveFont(11));
|
||||
add(exlab, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
protected JCheckBox _checked;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class FinishedPanel
|
||||
extends ImporterPanel
|
||||
implements ActionListener
|
||||
{
|
||||
public FinishedPanel (Entry entry)
|
||||
public FinishedPanel ()
|
||||
{
|
||||
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
|
||||
gl.setOffAxisPolicy(GroupLayout.STRETCH);
|
||||
@@ -35,7 +35,7 @@ public class FinishedPanel
|
||||
|
||||
// create our action buttons
|
||||
String[] labels = new String[]
|
||||
{ "Import another CD", "Edit/categorize CDs" };
|
||||
{ "Import more music", "Edit/categorize CDs" };
|
||||
String[] actions = new String[] { "import", "edit" };
|
||||
|
||||
for (int i = 0; i < labels.length; i++) {
|
||||
@@ -47,9 +47,6 @@ public class FinishedPanel
|
||||
|
||||
// add our panel to the main group
|
||||
add(ppanel);
|
||||
|
||||
// save this guy for later
|
||||
_entry = entry;
|
||||
}
|
||||
|
||||
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
|
||||
@@ -68,7 +65,7 @@ public class FinishedPanel
|
||||
System.exit(0);
|
||||
|
||||
} else if (cmd.equals("import")) {
|
||||
_frame.setPanel(new InsertCDPanel());
|
||||
_frame.setPanel(new StartPanel());
|
||||
|
||||
} else {
|
||||
System.out.println("Unknown action event: " + cmd);
|
||||
@@ -76,5 +73,4 @@ public class FinishedPanel
|
||||
}
|
||||
|
||||
protected ImporterFrame _frame;
|
||||
protected Entry _entry;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
//
|
||||
// $Id: ImportMusicPanel.java,v 1.7 2003/05/04 18:16:07 mdb Exp $
|
||||
|
||||
package robodj.importer;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import com.samskivert.net.cddb.CDDB;
|
||||
import com.samskivert.swing.*;
|
||||
import com.samskivert.swing.util.*;
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import org.farng.mp3.*;
|
||||
import org.farng.mp3.id3.*;
|
||||
|
||||
import robodj.convert.*;
|
||||
import robodj.repository.*;
|
||||
|
||||
public class ImportMusicPanel extends ImporterPanel
|
||||
implements ActionListener, TaskObserver
|
||||
{
|
||||
public ImportMusicPanel ()
|
||||
{
|
||||
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
|
||||
gl.setOffAxisPolicy(GroupLayout.STRETCH);
|
||||
setLayout(gl);
|
||||
|
||||
// create our label for the left hand side
|
||||
JLabel cdlabel = new JLabel("Import Music", JLabel.CENTER);
|
||||
cdlabel.setFont(new Font("Helvetica", Font.BOLD, 24));
|
||||
add(cdlabel, GroupLayout.FIXED);
|
||||
|
||||
// on the right hand side we want a status display to let the user
|
||||
// know how the ID3 reading process is going
|
||||
GroupLayout vgl = new VGroupLayout(GroupLayout.STRETCH);
|
||||
vgl.setOffAxisPolicy(GroupLayout.STRETCH);
|
||||
_spanel = new JPanel(vgl);
|
||||
|
||||
JLabel sl = new JLabel("Status", JLabel.LEFT);
|
||||
_spanel.add(sl, GroupLayout.FIXED);
|
||||
|
||||
// set up our status display
|
||||
_status.setText("Ready to import music...\n");
|
||||
_spanel.add(new JScrollPane(_status));
|
||||
|
||||
// add our panel to the main group
|
||||
add(_spanel);
|
||||
}
|
||||
|
||||
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
|
||||
{
|
||||
// keep this for later
|
||||
_frame = frame;
|
||||
|
||||
frame.addControlButton("Cancel", "cancel", this);
|
||||
frame.addControlButton("Back", "back", this);
|
||||
_next = frame.addControlButton("Next...", "next", this);
|
||||
_next.setEnabled(popped);
|
||||
|
||||
if (popped) {
|
||||
// clear out our song arrays
|
||||
for (Iterator iter = _byid.values().iterator(); iter.hasNext(); ) {
|
||||
((Entry)iter.next()).songs = null;
|
||||
}
|
||||
|
||||
} else {
|
||||
// create a file chooser dialog
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
|
||||
chooser.setMultiSelectionEnabled(true);
|
||||
chooser.setDialogTitle("Select files and directories to import...");
|
||||
|
||||
int rv = chooser.showDialog(this, "Import music");
|
||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
||||
// start scanning those directories for MP3 files
|
||||
final File[] files = chooser.getSelectedFiles();
|
||||
TaskMaster.invokeTask("scan", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
return scanForFiles(files);
|
||||
}
|
||||
}, this);
|
||||
|
||||
} else {
|
||||
// pop back to the last panel
|
||||
_frame.popPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void taskCompleted (String name, Object result)
|
||||
{
|
||||
if (name.equals("scan")) {
|
||||
final File[] files = (File[])result;
|
||||
// now fire up a task to read the ID3 info from these files
|
||||
TaskMaster.invokeTask("readid3", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
readID3(files);
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
|
||||
} else if (name.equals("readid3")) {
|
||||
// replace the info text with the track info table
|
||||
remove(_spanel);
|
||||
JPanel bits = new JPanel(new BorderLayout(5, 5));
|
||||
bits.add(new JLabel("Edit metadata"), BorderLayout.NORTH);
|
||||
JTable table = new JTable(
|
||||
new SongTableModel(_byid, _byname, _songs));
|
||||
bits.add(new JScrollPane(table), BorderLayout.CENTER);
|
||||
bits.add(new JLabel("You will select which albums to upload " +
|
||||
"on the next page..."), BorderLayout.SOUTH);
|
||||
add(bits);
|
||||
SwingUtil.sizeToContents(table);
|
||||
|
||||
// enable the next button
|
||||
_next.setEnabled(true);
|
||||
|
||||
// relay everything out
|
||||
validate();
|
||||
}
|
||||
}
|
||||
|
||||
public void taskFailed (String name, Throwable exception)
|
||||
{
|
||||
if (name.equals("scan")) {
|
||||
_status.append("\nUnable to scan for MP3 files:\n\n" +
|
||||
exception.getMessage());
|
||||
|
||||
} else if (name.equals("readid3")) {
|
||||
_status.append("\nFailed while reading ID3 tags:\n\n" +
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
String cmd = e.getActionCommand();
|
||||
if (cmd.equals("cancel")) {
|
||||
System.exit(0);
|
||||
|
||||
} else if (cmd.equals("back")) {
|
||||
// pop back to the last panel
|
||||
_frame.popPanel();
|
||||
|
||||
} else if (cmd.equals("next")) {
|
||||
entrifyAndContinue();
|
||||
|
||||
} else {
|
||||
System.out.println("Unknown action event: " + cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the supplied set of files and directories for all files that
|
||||
* appear to be MP3s.
|
||||
*
|
||||
* @return an array of all matched files.
|
||||
*/
|
||||
protected File[] scanForFiles (File[] seeds)
|
||||
{
|
||||
ArrayList matches = new ArrayList();
|
||||
scanForFiles(seeds, matches);
|
||||
return (File[])matches.toArray(new File[matches.size()]);
|
||||
}
|
||||
|
||||
protected void scanForFiles (File[] seeds, ArrayList matches)
|
||||
{
|
||||
for (int ii = 0; ii < seeds.length; ii++) {
|
||||
if (seeds[ii].isDirectory()) {
|
||||
postAsyncStatus("Scanning " + seeds[ii] + "...");
|
||||
scanForFiles(seeds[ii].listFiles(), matches);
|
||||
} else if (seeds[ii].getName().toLowerCase().endsWith(".mp3")) {
|
||||
matches.add(seeds[ii]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void readID3 (File[] files)
|
||||
{
|
||||
for (int ii = 0; ii < files.length; ii++) {
|
||||
try {
|
||||
MP3File mpf = new MP3File(files[ii]);
|
||||
String title = getID3Info(mpf, "TIT2", "title");
|
||||
String artist = getID3Info(mpf, "TPE1", "artist");
|
||||
String album = getID3Info(mpf, "TALB", "album");
|
||||
String track = getID3Info(mpf, "TRCK", "");
|
||||
|
||||
// locate the Entry associated with this album/artist,
|
||||
// creating a new one if necessary
|
||||
String key = SongTableModel.getKey(album, artist);
|
||||
Entry entry = (Entry)_byname.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry();
|
||||
entry.entryid = _byname.size(); // assign a temporary id
|
||||
entry.artist = nullToUnknown(artist);
|
||||
entry.title = nullToUnknown(album);
|
||||
entry.source = "import";
|
||||
_byname.put(key, entry);
|
||||
_byid.put(entry.entryid, entry);
|
||||
postAsyncStatus("Located album '" + album + "' by '" +
|
||||
artist + "'.");
|
||||
}
|
||||
|
||||
Song song = new Song();
|
||||
song.entryid = entry.entryid;
|
||||
song.title = StringUtil.blank(title) ?
|
||||
stripSuffix(files[ii].getName()) : title;
|
||||
song.location = files[ii].toString();
|
||||
|
||||
// try to figure out what the track number is
|
||||
if (StringUtil.blank(track)) {
|
||||
Matcher m = _trackNo.matcher(files[ii].getName());
|
||||
if (m.matches()) {
|
||||
track = m.group(1);
|
||||
}
|
||||
}
|
||||
try {
|
||||
song.position = Integer.parseInt(track);
|
||||
} catch (Throwable t) {
|
||||
song.position = 1;
|
||||
}
|
||||
|
||||
// finally slap that song on the list
|
||||
_songs.add(song);
|
||||
postAsyncStatus("Located song '" + title + "' " +
|
||||
"(" + song.position + ").");
|
||||
|
||||
} catch (Exception e) {
|
||||
postAsyncStatus("Failure processing " + files[ii] + ": " + e);
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String nullToUnknown (String value)
|
||||
{
|
||||
return StringUtil.blank(value) ? "Unknown" : value;
|
||||
}
|
||||
|
||||
protected String stripSuffix (String value)
|
||||
{
|
||||
int didx = value.lastIndexOf(".");
|
||||
return (didx == -1) ? value : value.substring(0, didx);
|
||||
}
|
||||
|
||||
protected String getID3Info (MP3File file, String v2tag, String v1tag)
|
||||
{
|
||||
// first look for the v2 tag
|
||||
AbstractID3v2 tag2 = file.getID3v2Tag();
|
||||
String value = null;
|
||||
if (tag2 != null) {
|
||||
AbstractMP3Fragment frame = tag2.getFrame(v2tag);
|
||||
if (frame != null) {
|
||||
value = ((AbstractFrameBodyTextInformation)
|
||||
frame.getBody()).getText();
|
||||
}
|
||||
if (!StringUtil.blank(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// if that fails, try the v1 tag
|
||||
ID3v1 tag1 = file.getID3v1Tag();
|
||||
if (tag1 != null) {
|
||||
if (v1tag.equals("album")) {
|
||||
return tag1.getAlbum();
|
||||
} else if (v1tag.equals("artist")) {
|
||||
return tag1.getArtist();
|
||||
} else if (v1tag.equals("title")) {
|
||||
return tag1.getTitle();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void entrifyAndContinue ()
|
||||
{
|
||||
// go through our songs and collect them into their Entry objects
|
||||
ArrayList elist = new ArrayList();
|
||||
for (Iterator iter = _songs.iterator(); iter.hasNext(); ) {
|
||||
Song song = (Song)iter.next();
|
||||
Entry entry = (Entry)_byid.get(song.entryid);
|
||||
if (entry.songs == null) {
|
||||
entry.songs = new Song[] { song };
|
||||
} else {
|
||||
entry.songs = (Song[])ArrayUtil.append(entry.songs, song);
|
||||
}
|
||||
if (!elist.contains(entry)) {
|
||||
elist.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// create the upload panel and pass the entries along
|
||||
Entry[] entries = (Entry[])elist.toArray(new Entry[elist.size()]);
|
||||
_frame.pushPanel(new UploadMusicPanel(entries));
|
||||
}
|
||||
|
||||
protected ImporterFrame _frame;
|
||||
protected JButton _next;
|
||||
protected JPanel _spanel;
|
||||
|
||||
protected HashMap _byname = new HashMap();
|
||||
protected HashIntMap _byid = new HashIntMap();
|
||||
protected ArrayList _songs = new ArrayList();
|
||||
|
||||
protected static Pattern _trackNo;
|
||||
static {
|
||||
try {
|
||||
_trackNo = Pattern.compile("\\s(\\d+)\\s");
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,11 +67,10 @@ public class Importer
|
||||
|
||||
// create our frame and first panel
|
||||
ImporterFrame frame = new ImporterFrame();
|
||||
InsertCDPanel panel = new InsertCDPanel();
|
||||
frame.pushPanel(panel);
|
||||
frame.pushPanel(new StartPanel());
|
||||
|
||||
// center the frame in the screen and show it
|
||||
frame.setSize(640, 480);
|
||||
frame.setSize(1024, 600);
|
||||
SwingUtil.centerWindow(frame);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,28 @@
|
||||
|
||||
package robodj.importer;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.LayoutManager;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
public abstract class ImporterPanel extends JPanel
|
||||
{
|
||||
public ImporterPanel ()
|
||||
{
|
||||
this(new BorderLayout());
|
||||
}
|
||||
|
||||
public ImporterPanel (LayoutManager layout)
|
||||
{
|
||||
super(layout);
|
||||
|
||||
_status = new JTextArea();
|
||||
_status.setLineWrap(true);
|
||||
_status.setEditable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,4 +36,24 @@ public abstract class ImporterPanel extends JPanel
|
||||
* than added to the frame for the first time.
|
||||
*/
|
||||
public abstract void wasAddedToFrame (ImporterFrame frame, boolean popped);
|
||||
|
||||
/**
|
||||
* Displays the supplied string in the status field and scrolls the
|
||||
* status field to the bottom.
|
||||
*/
|
||||
protected void postAsyncStatus (final String status)
|
||||
{
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run ()
|
||||
{
|
||||
_status.append(status + "\n");
|
||||
Rectangle bot = _status.getBounds();
|
||||
bot.y = bot.height-1;
|
||||
bot.height = 1;
|
||||
_status.scrollRectToVisible(bot);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected JTextArea _status;
|
||||
}
|
||||
|
||||
@@ -60,11 +60,8 @@ public class RipPanel
|
||||
// create the status log
|
||||
JLabel sl = new JLabel("Status", JLabel.LEFT);
|
||||
ppanel.add(sl, GroupLayout.FIXED);
|
||||
_statusLog = new JTextArea();
|
||||
_statusLog.setLineWrap(true);
|
||||
_statusLog.setEditable(false);
|
||||
// make something for it to scroll around in
|
||||
ppanel.add(new JScrollPane(_statusLog));
|
||||
ppanel.add(new JScrollPane(_status));
|
||||
|
||||
// add our panel to the main group
|
||||
add(ppanel);
|
||||
@@ -74,16 +71,6 @@ public class RipPanel
|
||||
_entry = entry;
|
||||
}
|
||||
|
||||
protected void postAsyncStatus (final String status)
|
||||
{
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run ()
|
||||
{
|
||||
_statusLog.append(status + "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void postAsyncProgress (final int overallComplete,
|
||||
final int taskComplete)
|
||||
{
|
||||
@@ -390,7 +377,7 @@ public class RipPanel
|
||||
} else {
|
||||
msg = exception.toString();
|
||||
}
|
||||
_statusLog.append("Error: " + msg + "\n");
|
||||
_status.append("Error: " + msg + "\n");
|
||||
Log.logStackTrace(exception);
|
||||
}
|
||||
|
||||
@@ -401,7 +388,7 @@ public class RipPanel
|
||||
System.exit(0);
|
||||
|
||||
} else if (cmd.equals("next")) {
|
||||
_frame.pushPanel(new FinishedPanel(_entry));
|
||||
_frame.pushPanel(new FinishedPanel());
|
||||
|
||||
} else {
|
||||
System.out.println("Unknown action event: " + cmd);
|
||||
@@ -424,7 +411,6 @@ public class RipPanel
|
||||
|
||||
protected JProgressBar _oprogress;
|
||||
protected JProgressBar _progress;
|
||||
protected JTextArea _statusLog;
|
||||
|
||||
protected Ripper.TrackInfo[] _info;
|
||||
protected Entry _entry;
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.importer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.QuickSort;
|
||||
|
||||
import robodj.repository.Entry;
|
||||
import robodj.repository.Song;
|
||||
|
||||
/**
|
||||
* Allows the display and editing of a big list of songs.
|
||||
*/
|
||||
public class SongTableModel extends AbstractTableModel
|
||||
{
|
||||
/**
|
||||
* Creates a song table model with the supplied set of entries and
|
||||
* songs.
|
||||
*/
|
||||
public SongTableModel (HashIntMap byid, HashMap byname, ArrayList songs)
|
||||
{
|
||||
_byid = byid;
|
||||
_byname = byname;
|
||||
_songs = songs;
|
||||
QuickSort.sort(_songs, SONG_COMP);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Class getColumnClass (int column)
|
||||
{
|
||||
return CLASSES[column];
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public String getColumnName (int column)
|
||||
{
|
||||
return COLUMNS[column];
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int getRowCount()
|
||||
{
|
||||
return _songs.size();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int getColumnCount()
|
||||
{
|
||||
return COLUMNS.length;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Object getValueAt (int row, int column) {
|
||||
Song song = (Song)_songs.get(row);
|
||||
Entry entry = (Entry)_byid.get(song.entryid);
|
||||
|
||||
switch (column) {
|
||||
case 0: return entry.artist;
|
||||
case 1: return entry.title;
|
||||
case 2: return song.title;
|
||||
case 3: return new Integer(song.position);
|
||||
default: return "<error:" + column + ">";
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean isCellEditable (int row, int column) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void setValueAt (Object value, int row, int column) {
|
||||
Song song = (Song)_songs.get(row);
|
||||
|
||||
switch (column) {
|
||||
case 0: remapSong(song, null, (String)value); break;
|
||||
case 1: remapSong(song, (String)value, null); break;
|
||||
case 2: song.title = (String)value; return; // avoid fire..Changed
|
||||
case 3: song.position = ((Integer)value).intValue(); break;
|
||||
default:
|
||||
System.err.println("Refusing to update bogus column [row=" + row +
|
||||
", column=" + column + ", val=" + value + "].");
|
||||
break;
|
||||
}
|
||||
|
||||
QuickSort.sort(_songs, SONG_COMP);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/** Updates the album title or artist for the supplied song. */
|
||||
protected void remapSong (Song song, String album, String artist)
|
||||
{
|
||||
Entry entry = (Entry)_byid.get(song.entryid);
|
||||
|
||||
// fill in any unknown data
|
||||
if (album == null) {
|
||||
album = entry.title;
|
||||
}
|
||||
if (artist == null) {
|
||||
artist = entry.artist;
|
||||
}
|
||||
String key = getKey(album, artist);
|
||||
System.err.println("Considering " + key);
|
||||
|
||||
// if neither album or artist are "Unknown", and the new name does
|
||||
// not already exist as an entry, then just update this entry
|
||||
if (!entry.title.equals("Unknown") && !entry.artist.equals("Unknown") &&
|
||||
!_byname.containsKey(key)) {
|
||||
_byname.remove(getKey(entry)); // remove the old key
|
||||
entry.title = (album == null) ? entry.title : album;
|
||||
entry.artist = (artist == null) ? entry.artist : artist;
|
||||
_byname.put(key, entry); // map with the new key
|
||||
return;
|
||||
}
|
||||
|
||||
// look up (or create) a new entry
|
||||
Entry nentry = (Entry)_byname.get(key);
|
||||
if (nentry == null) {
|
||||
nentry = clone(entry);
|
||||
nentry.title = album;
|
||||
nentry.artist = artist;
|
||||
nentry.entryid = _byname.size();
|
||||
_byname.put(getKey(nentry), nentry);
|
||||
_byid.put(nentry.entryid, nentry);
|
||||
}
|
||||
song.entryid = nentry.entryid;
|
||||
System.err.println("Remapped " + song);
|
||||
}
|
||||
|
||||
/** Duplicates the supplied entry. */
|
||||
protected Entry clone (Entry source)
|
||||
{
|
||||
Entry nentry = new Entry();
|
||||
nentry.title = source.title;
|
||||
nentry.artist = source.artist;
|
||||
nentry.source = source.source;
|
||||
return nentry;
|
||||
}
|
||||
|
||||
/** Creates a key from the entry title and artist. */
|
||||
protected static String getKey (Entry entry)
|
||||
{
|
||||
return getKey(entry.title, entry.artist);
|
||||
}
|
||||
|
||||
/** Creates a key from the entry title and artist. */
|
||||
protected static String getKey (String title, String artist)
|
||||
{
|
||||
return title + ":" + artist;
|
||||
}
|
||||
|
||||
protected HashMap _byname = new HashMap();
|
||||
protected HashIntMap _byid = new HashIntMap();
|
||||
protected ArrayList _songs;
|
||||
|
||||
protected Comparator SONG_COMP = new Comparator() {
|
||||
public int compare (Object a, Object b) {
|
||||
Song sa = (Song)a, sb = (Song)b;
|
||||
Entry ea = (Entry)_byid.get(sa.entryid);
|
||||
Entry eb = (Entry)_byid.get(sb.entryid);
|
||||
int comp;
|
||||
if ((comp = ea.artist.compareTo(eb.artist)) != 0) {
|
||||
return comp;
|
||||
}
|
||||
if ((comp = ea.title.compareTo(eb.title)) != 0) {
|
||||
return comp;
|
||||
}
|
||||
if (sa.position != sb.position) {
|
||||
return sa.position - sb.position;
|
||||
}
|
||||
return sa.title.compareTo(sb.title);
|
||||
}
|
||||
};
|
||||
|
||||
protected static final String[] COLUMNS = {
|
||||
"Artist", "Album", "Title", "#"
|
||||
};
|
||||
|
||||
protected static final Class[] CLASSES = {
|
||||
String.class, String.class, String.class, Integer.class
|
||||
};
|
||||
}
|
||||
+28
-9
@@ -1,19 +1,22 @@
|
||||
//
|
||||
// $Id: InsertCDPanel.java,v 1.3 2002/03/03 21:17:03 mdb Exp $
|
||||
// $Id: StartPanel.java,v 1.3 2002/03/03 21:17:03 mdb Exp $
|
||||
|
||||
package robodj.importer;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class InsertCDPanel
|
||||
extends ImporterPanel
|
||||
import com.samskivert.swing.VGroupLayout;
|
||||
|
||||
public class StartPanel extends ImporterPanel
|
||||
implements ActionListener
|
||||
{
|
||||
public InsertCDPanel ()
|
||||
public StartPanel ()
|
||||
{
|
||||
super(new GridLayout(1, 2));
|
||||
|
||||
@@ -24,10 +27,21 @@ public class InsertCDPanel
|
||||
JLabel cdlabel = new JLabel(cdicon);
|
||||
add(cdlabel);
|
||||
|
||||
// create the "insert cd" text label for the right hand side
|
||||
JLabel ilabel = new JLabel("Please insert the CD...",
|
||||
JLabel.CENTER);
|
||||
add(ilabel);
|
||||
// allow the user to select whether they want to rip a CD or
|
||||
// import music directly
|
||||
VGroupLayout vgl = new VGroupLayout();
|
||||
vgl.setOffAxisJustification(VGroupLayout.LEFT);
|
||||
JPanel bpanel = new JPanel(vgl);
|
||||
bpanel.add(_rip = new JRadioButton("Rip a CD (insert CD now)"));
|
||||
_rip.setMnemonic(KeyEvent.VK_R);
|
||||
_rip.setSelected(true);
|
||||
bpanel.add(_imp = new JRadioButton("Import music"));
|
||||
_imp.setMnemonic(KeyEvent.VK_M);
|
||||
add(bpanel);
|
||||
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
group.add(_imp);
|
||||
group.add(_rip);
|
||||
|
||||
// give ourselves a wee bit of a border
|
||||
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
@@ -49,12 +63,17 @@ public class InsertCDPanel
|
||||
System.exit(0);
|
||||
|
||||
} else if (cmd.equals("next")) {
|
||||
_frame.pushPanel(new CDDBLookupPanel());
|
||||
if (_rip.isSelected()) {
|
||||
_frame.pushPanel(new CDDBLookupPanel());
|
||||
} else {
|
||||
_frame.pushPanel(new ImportMusicPanel());
|
||||
}
|
||||
|
||||
} else {
|
||||
System.out.println("Unknown action event: " + cmd);
|
||||
}
|
||||
}
|
||||
|
||||
protected JRadioButton _imp, _rip;
|
||||
protected ImporterFrame _frame;
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// $Id: UploadMusicPanel.java,v 1.7 2003/05/04 18:16:07 mdb Exp $
|
||||
|
||||
package robodj.importer;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import com.samskivert.net.cddb.CDDB;
|
||||
import com.samskivert.swing.*;
|
||||
import com.samskivert.swing.util.*;
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import org.farng.mp3.*;
|
||||
import org.farng.mp3.id3.*;
|
||||
|
||||
import robodj.convert.*;
|
||||
import robodj.repository.*;
|
||||
import com.samskivert.util.CollectionUtil;
|
||||
|
||||
public class UploadMusicPanel extends ImporterPanel
|
||||
implements ActionListener, TaskObserver
|
||||
{
|
||||
public UploadMusicPanel (Entry[] entries)
|
||||
{
|
||||
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
|
||||
gl.setOffAxisPolicy(GroupLayout.STRETCH);
|
||||
setLayout(gl);
|
||||
|
||||
// save these for later
|
||||
_entries = entries;
|
||||
|
||||
// create our label for the left hand side
|
||||
JLabel cdlabel = new JLabel("Upload Music", JLabel.CENTER);
|
||||
cdlabel.setFont(new Font("Helvetica", Font.BOLD, 24));
|
||||
add(cdlabel, GroupLayout.FIXED);
|
||||
|
||||
// display status on looking up similar albums
|
||||
GroupLayout vgl = new VGroupLayout(GroupLayout.STRETCH);
|
||||
vgl.setOffAxisPolicy(GroupLayout.STRETCH);
|
||||
_spanel = new JPanel(vgl);
|
||||
|
||||
JLabel sl = new JLabel("Status", JLabel.LEFT);
|
||||
_spanel.add(sl, GroupLayout.FIXED);
|
||||
|
||||
_status.setText("Checking for similar music...\n");
|
||||
_spanel.add(new JScrollPane(_status));
|
||||
|
||||
// add our panel to the main group
|
||||
add(_spanel);
|
||||
}
|
||||
|
||||
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
|
||||
{
|
||||
// keep this for later
|
||||
_frame = frame;
|
||||
|
||||
frame.addControlButton("Cancel", "cancel", this);
|
||||
_back = frame.addControlButton("Back", "back", this);
|
||||
_next = frame.addControlButton("Upload", "upload", this);
|
||||
|
||||
// fire up the similarity checker task
|
||||
TaskMaster.invokeTask("checkrepo", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
checkForSimilar();
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
public void taskCompleted (String name, Object result)
|
||||
{
|
||||
if (name.equals("checkrepo")) {
|
||||
// display the entries and their similarity matches
|
||||
remove(_spanel);
|
||||
JPanel panel = GroupLayout.makeVBox(
|
||||
GroupLayout.NONE, GroupLayout.TOP, GroupLayout.EQUALIZE);
|
||||
((GroupLayout)panel.getLayout()).setOffAxisJustification(
|
||||
GroupLayout.LEFT);
|
||||
panel.add(new JLabel("Select the albums to upload..."));
|
||||
panel.add(new JSeparator());
|
||||
for (int ii = 0; ii < _entries.length; ii++) {
|
||||
panel.add(new EntryUploader(
|
||||
_entries[ii], (ArrayList)
|
||||
_similar.get(_entries[ii].entryid)));
|
||||
}
|
||||
add(new JScrollPane(panel));
|
||||
|
||||
// enable the next button
|
||||
_next.setEnabled(true);
|
||||
|
||||
// relay everything out
|
||||
validate();
|
||||
|
||||
} else if (name.equals("upload")) {
|
||||
_next.setEnabled(true);
|
||||
_next.setActionCommand("next");
|
||||
_next.setText("Next...");
|
||||
}
|
||||
}
|
||||
|
||||
public void taskFailed (String name, Throwable exception)
|
||||
{
|
||||
if (name.equals("checkrepo")) {
|
||||
_status.append("\nUnable to check repository for " +
|
||||
"similar music:\n\n" + exception.getMessage());
|
||||
|
||||
} else if (name.equals("readid3")) {
|
||||
_status.append("\nFailed while reading ID3 tags:\n\n" +
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
String cmd = e.getActionCommand();
|
||||
if (cmd.equals("cancel")) {
|
||||
System.exit(0);
|
||||
|
||||
} else if (cmd.equals("back")) {
|
||||
// pop back to the last panel
|
||||
_frame.popPanel();
|
||||
|
||||
} else if (cmd.equals("upload")) {
|
||||
_next.setEnabled(false);
|
||||
_back.setEnabled(false);
|
||||
TaskMaster.invokeTask("upload", new TaskAdapter() {
|
||||
public Object invoke () throws Exception {
|
||||
performUpload();
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
|
||||
} else if (cmd.equals("next")) {
|
||||
_frame.pushPanel(new FinishedPanel());
|
||||
|
||||
} else {
|
||||
System.out.println("Unknown action event: " + cmd);
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkForSimilar ()
|
||||
{
|
||||
for (int ii = 0; ii < _entries.length; ii++) {
|
||||
Entry entry = _entries[ii];
|
||||
postAsyncStatus("Checking for music like '" + entry.title +
|
||||
"' - '" +entry.artist + "'.");
|
||||
ArrayList similar = new ArrayList();
|
||||
try {
|
||||
String eartist = StringUtil.replace(entry.artist, "'", "\\'");
|
||||
Entry[] artist =
|
||||
Importer.repository.getEntries(
|
||||
"where soundex(artist) = soundex('" + eartist + "')");
|
||||
CollectionUtil.addAll(similar, artist);
|
||||
String etitle = StringUtil.replace(entry.title, "'", "\\'");
|
||||
Entry[] title =
|
||||
Importer.repository.getEntries(
|
||||
"where soundex(title) = soundex('" + etitle + "')");
|
||||
CollectionUtil.addAll(similar, title);
|
||||
_similar.put(entry.entryid, similar);
|
||||
|
||||
} catch (Exception e) {
|
||||
postAsyncStatus("Choked checking repository: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void performUpload ()
|
||||
{
|
||||
}
|
||||
|
||||
protected ImporterFrame _frame;
|
||||
protected JButton _next, _back;
|
||||
protected JPanel _spanel;
|
||||
|
||||
protected Entry[] _entries;
|
||||
protected HashIntMap _similar = new HashIntMap();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.server;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService.ResultListener;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import robodj.client.DJService;
|
||||
import robodj.data.DJMarshaller;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link DJProvider}.
|
||||
*/
|
||||
public class DJDispatcher extends InvocationDispatcher
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public DJDispatcher (DJProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public InvocationMarshaller createMarshaller ()
|
||||
{
|
||||
return new DJMarshaller();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case DJMarshaller.GET_DJOID:
|
||||
((DJProvider)provider).getDJOid(
|
||||
source,
|
||||
(ResultListener)args[0]
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.server;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
import robodj.client.DJService;
|
||||
|
||||
/**
|
||||
* Implements the main RoboDJ services.
|
||||
*/
|
||||
public class DJProvider implements InvocationProvider
|
||||
{
|
||||
/** Used to obtain the oid of the DJ object. */
|
||||
public void getDJOid (ClientObject caller,
|
||||
DJService.ResultListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
listener.requestProcessed(new Integer(MusicDaemon.djobj.getOid()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.server;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.StaticConnectionProvider;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.server.PresentsServer;
|
||||
|
||||
import robodj.Log;
|
||||
import robodj.data.DJObject;
|
||||
import robodj.repository.Repository;
|
||||
import robodj.util.ErrorUtil;
|
||||
import robodj.util.RDJPrefs;
|
||||
import robodj.util.RDJPrefsPanel;
|
||||
|
||||
/**
|
||||
* The main entry point for the music playing daemon.
|
||||
*/
|
||||
public class MusicDaemon extends PresentsServer
|
||||
{
|
||||
/** Provides access to the main RoboDJ services. */
|
||||
public static DJProvider djprov = new DJProvider();
|
||||
|
||||
/** The entity that actually plays the music in the playlist. */
|
||||
public static MusicPlayer player = new MusicPlayer();
|
||||
|
||||
/** Our shared server state. */
|
||||
public static DJObject djobj;
|
||||
|
||||
/** Our music repository. */
|
||||
public static Repository repo;
|
||||
|
||||
// documentation inherited
|
||||
public void init ()
|
||||
throws Exception
|
||||
{
|
||||
super.init();
|
||||
|
||||
// loop until the user provides us with a configuration that works
|
||||
// or requests to exit
|
||||
for (int ii = 0; ii < 100; ii++) {
|
||||
String repodir = RDJPrefs.getRepositoryDirectory();
|
||||
if (StringUtil.blank(repodir) || ii > 0) {
|
||||
// display the initial configuration wizard if we are not
|
||||
// yet properly configured
|
||||
RDJPrefsPanel.display(true);
|
||||
}
|
||||
|
||||
// create our repository
|
||||
try {
|
||||
repo = new Repository(
|
||||
new StaticConnectionProvider(RDJPrefs.getJDBCConfig()));
|
||||
|
||||
} catch (PersistenceException pe) {
|
||||
String errmsg = "Unable to communicate with database:";
|
||||
if (ErrorUtil.reportError(errmsg, pe)) {
|
||||
System.exit(-1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// create and register our providers
|
||||
invmgr.registerDispatcher(new DJDispatcher(djprov), true);
|
||||
|
||||
// create our DJ object
|
||||
omgr.createObject(DJObject.class, new Subscriber() {
|
||||
public void objectAvailable (DObject object) {
|
||||
djobj = (DJObject)object;
|
||||
player.init(djobj);
|
||||
Log.info("Music daemon ready [oid=" + djobj.getOid() + "].");
|
||||
}
|
||||
public void requestFailed (int oid, ObjectAccessException cause) {
|
||||
Log.warning("Failed to create DJ object: " + cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
Log.info("RoboDJ music server starting...");
|
||||
|
||||
MusicDaemon server = new MusicDaemon();
|
||||
try {
|
||||
// initialize the server
|
||||
server.init();
|
||||
// start the server to running (this method call won't return
|
||||
// until the server is shut down)
|
||||
server.run();
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.warning("Unable to initialize server.");
|
||||
Log.logStackTrace(e);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package robodj.server;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
|
||||
import javazoom.jl.player.Player;
|
||||
|
||||
import com.threerings.presents.dobj.AttributeChangeListener;
|
||||
import com.threerings.presents.dobj.AttributeChangedEvent;
|
||||
|
||||
import robodj.Log;
|
||||
import robodj.data.DJObject;
|
||||
import robodj.data.PlaylistEntry;
|
||||
import robodj.repository.Entry;
|
||||
import robodj.repository.Song;
|
||||
|
||||
/**
|
||||
* Handles the actual playing of music in the playlist.
|
||||
*/
|
||||
public class MusicPlayer extends Thread
|
||||
implements AttributeChangeListener
|
||||
{
|
||||
public MusicPlayer ()
|
||||
{
|
||||
// start up the music playing thread
|
||||
setDaemon(true);
|
||||
start();
|
||||
}
|
||||
|
||||
public void init (DJObject djobj)
|
||||
{
|
||||
_djobj = djobj;
|
||||
_djobj.addListener(this);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void attributeChanged (AttributeChangedEvent event)
|
||||
{
|
||||
if (event.getName().equals(DJObject.PLAYING)) {
|
||||
playEntry(_djobj.getPlaying());
|
||||
recomputeNext();
|
||||
|
||||
} else if (event.getName().equals(DJObject.PLAYLIST)) {
|
||||
recomputeNext();
|
||||
}
|
||||
}
|
||||
|
||||
public void run ()
|
||||
{
|
||||
while (true) {
|
||||
try {
|
||||
// this will block if there's nothing to play
|
||||
Player player = getPlaying();
|
||||
Log.info("Playing " + _pentry + "...");
|
||||
while (player.play(PLAY_FRAMES)) {
|
||||
// loop!
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
Log.warning("Music player choked.");
|
||||
Log.logStackTrace(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the specified playlist entry as the currently playing
|
||||
* entry. Even if we are currently playing the same entry, we will
|
||||
* restart play from the beginning.
|
||||
*/
|
||||
protected synchronized void playEntry (PlaylistEntry entry)
|
||||
{
|
||||
// if we're already playing what we want to be playing, stop
|
||||
if (equals(_pentry, entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.info("Forcing play [entry=" + entry + "].");
|
||||
|
||||
// queue up the next track
|
||||
_fnentry = entry;
|
||||
_fnext = getPlayer(_fnentry);
|
||||
|
||||
// clear out any current track
|
||||
if (_playing != null) {
|
||||
Log.info("Stopping current track.");
|
||||
_playing.close();
|
||||
}
|
||||
|
||||
// finally wake up the player in case it is sleeping
|
||||
notify();
|
||||
}
|
||||
|
||||
protected synchronized void recomputeNext ()
|
||||
{
|
||||
// if we're properly wired up, NOOP
|
||||
PlaylistEntry nentry = _djobj.getNext();
|
||||
if (equals(_nentry, nentry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clear things out in preparation
|
||||
if (_next != null) {
|
||||
_next.close();
|
||||
_next = null;
|
||||
}
|
||||
|
||||
// compute and load up info on the next playing song
|
||||
_nentry = nentry;
|
||||
Log.info("Queuing up " + _nentry + "...");
|
||||
_next = getPlayer(_nentry);
|
||||
|
||||
// wake up the music player if necessary
|
||||
notify();
|
||||
}
|
||||
|
||||
protected Player getPlayer (PlaylistEntry entry)
|
||||
{
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Song song = null;
|
||||
try {
|
||||
Entry album = MusicDaemon.repo.getEntry(entry.entryId);
|
||||
if (album != null) {
|
||||
song = album.getSong(entry.songId);
|
||||
}
|
||||
if (album == null || song == null) {
|
||||
Log.warning("Bogus playlist entry! [entry=" + entry +
|
||||
", album=" + album + ", song=" + song + "].");
|
||||
return null;
|
||||
}
|
||||
return new Player(new FileInputStream(song.location));
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.warning("Failed to load song [pentry=" + entry +
|
||||
", song=" + song + "].");
|
||||
Log.logStackTrace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized Player getPlaying ()
|
||||
{
|
||||
while (_next == null && _fnext == null) {
|
||||
try {
|
||||
Log.info("Waiting for track...");
|
||||
wait();
|
||||
} catch (InterruptedException ie) {
|
||||
Log.warning("Interrupted waiting for track to play.");
|
||||
}
|
||||
}
|
||||
|
||||
if (_fnext != null) {
|
||||
_playing = _fnext;
|
||||
_pentry = _fnentry;
|
||||
_fnext = null;
|
||||
_fnentry = null;
|
||||
} else {
|
||||
_playing = _next;
|
||||
_pentry = _nentry;
|
||||
_nentry = null;
|
||||
_next = null;
|
||||
_djobj.skip(); // move forward in the playlist
|
||||
}
|
||||
return _playing;
|
||||
}
|
||||
|
||||
/** Helper function. */
|
||||
protected boolean equals (PlaylistEntry e1, PlaylistEntry e2)
|
||||
{
|
||||
return (e1 == e2 || (e1 != null && e1.equals(e2)));
|
||||
}
|
||||
|
||||
protected DJObject _djobj;
|
||||
|
||||
protected Player _playing;
|
||||
protected PlaylistEntry _pentry;
|
||||
|
||||
protected Player _next, _fnext;
|
||||
protected int _nidx;
|
||||
protected PlaylistEntry _nentry, _fnentry;
|
||||
|
||||
/** The number of frames to play before checking up on things. */
|
||||
protected static final int PLAY_FRAMES = 10;
|
||||
}
|
||||
@@ -168,6 +168,11 @@ public class RDJPrefsPanel extends JPanel
|
||||
protected String _prefKey;
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
display(true);
|
||||
}
|
||||
|
||||
protected JFrame _frame;
|
||||
|
||||
/** Defines our configurable preferences. */
|
||||
|
||||
Reference in New Issue
Block a user