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="build.compiler" value="jikes"/>
|
||||||
<property name="web.home" value="/export/samskivert/pages/code"/>
|
<property name="web.home" value="/export/samskivert/pages/code"/>
|
||||||
<property name="install.dir" value="${web.home}/${app.name}"/>
|
<property name="install.dir" value="${web.home}/${app.name}"/>
|
||||||
|
<property name="narya.home" value="../../work/narya"/>
|
||||||
|
|
||||||
<!-- we want to access the environment -->
|
<!-- we want to access the environment -->
|
||||||
<property environment="env"/>
|
<property environment="env"/>
|
||||||
@@ -21,22 +22,51 @@
|
|||||||
<property name="javadoc.dir" value="${deploy.dir}/docs"/>
|
<property name="javadoc.dir" value="${deploy.dir}/docs"/>
|
||||||
<property name="doc.packages" value="robodj.*"/>
|
<property name="doc.packages" value="robodj.*"/>
|
||||||
<property name="doc.overview" value="robodj/overview.html"/>
|
<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 -->
|
<!-- declare our classpath -->
|
||||||
<path id="classpath">
|
<path id="classpath">
|
||||||
<fileset dir="${java.libraries}" includesfile="lib/CLIENT_LIBS"/>
|
<fileset dir="${java.libraries}" includesfile="lib/CLIENT_LIBS"/>
|
||||||
<fileset dir="lib" includes="**/*.jar"/>
|
<fileset dir="lib" includes="**/*.jar"/>
|
||||||
<pathelement location="${deploy.dir}/classes"/>
|
<pathelement location="${classes.dir}"/>
|
||||||
</path>
|
</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 -->
|
<!-- prepares the application directories -->
|
||||||
<target name="prepare">
|
<target name="prepare" depends="gendobj">
|
||||||
<mkdir dir="${deploy.dir}"/>
|
<mkdir dir="${deploy.dir}"/>
|
||||||
<mkdir dir="${deploy.dir}/classes"/>
|
<mkdir dir="${classes.dir}"/>
|
||||||
<mkdir dir="${javadoc.dir}"/>
|
<mkdir dir="${javadoc.dir}"/>
|
||||||
<!-- copy media and properties into the target directory -->
|
<!-- 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="**/*.jpg"/>
|
||||||
<fileset dir="${src.dir}" includes="**/*.png"/>
|
<fileset dir="${src.dir}" includes="**/*.png"/>
|
||||||
<fileset dir="${src.dir}" includes="**/*.properties"/>
|
<fileset dir="${src.dir}" includes="**/*.properties"/>
|
||||||
@@ -55,7 +85,7 @@
|
|||||||
|
|
||||||
<!-- build the java class files -->
|
<!-- build the java class files -->
|
||||||
<target name="compile" depends="prepare">
|
<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">
|
debug="on" optimize="off" deprecation="off">
|
||||||
<classpath refid="classpath"/>
|
<classpath refid="classpath"/>
|
||||||
</javac>
|
</javac>
|
||||||
@@ -68,7 +98,6 @@
|
|||||||
windowtitle="${app.name} API"
|
windowtitle="${app.name} API"
|
||||||
doctitle="${app.name} API"
|
doctitle="${app.name} API"
|
||||||
overview="${src.dir}/${doc.overview}"
|
overview="${src.dir}/${doc.overview}"
|
||||||
bottom="Copyright © 2002 ${copyright.holder}. All Rights Reserved."
|
|
||||||
destdir="${javadoc.dir}">
|
destdir="${javadoc.dir}">
|
||||||
<classpath refid="classpath"/>
|
<classpath refid="classpath"/>
|
||||||
<link href="http://www.waywardgeeks.org/code/samskivert/javadoc"/>
|
<link href="http://www.waywardgeeks.org/code/samskivert/javadoc"/>
|
||||||
@@ -82,7 +111,7 @@
|
|||||||
<!-- builds our distribution files (war and jar) -->
|
<!-- builds our distribution files (war and jar) -->
|
||||||
<target name="dist" depends="prepare,compile">
|
<target name="dist" depends="prepare,compile">
|
||||||
<jar destfile="${deploy.dir}/${dist.jar}"
|
<jar destfile="${deploy.dir}/${dist.jar}"
|
||||||
basedir="${deploy.dir}/classes"/>
|
basedir="${classes.dir}"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<!-- installs the JWS app -->
|
<!-- installs the JWS app -->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.io.IOException;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
import com.samskivert.io.PersistenceException;
|
import com.samskivert.io.PersistenceException;
|
||||||
@@ -18,7 +19,16 @@ import com.samskivert.util.ConfigUtil;
|
|||||||
import com.samskivert.util.PropertiesUtil;
|
import com.samskivert.util.PropertiesUtil;
|
||||||
import com.samskivert.util.RunAnywhere;
|
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.Log;
|
||||||
|
import robodj.client.DJService;
|
||||||
|
import robodj.data.DJObject;
|
||||||
import robodj.repository.Model;
|
import robodj.repository.Model;
|
||||||
import robodj.repository.Repository;
|
import robodj.repository.Repository;
|
||||||
import robodj.util.ErrorUtil;
|
import robodj.util.ErrorUtil;
|
||||||
@@ -31,16 +41,19 @@ import robodj.util.ServerControl;
|
|||||||
* collection and managing the playlist.
|
* collection and managing the playlist.
|
||||||
*/
|
*/
|
||||||
public class Chooser
|
public class Chooser
|
||||||
|
implements Client.Invoker, ClientObserver, Subscriber
|
||||||
{
|
{
|
||||||
public static Repository repository;
|
public static Repository repository;
|
||||||
|
|
||||||
public static Model model;
|
public static Model model;
|
||||||
|
|
||||||
public static ServerControl scontrol;
|
public static Client client;
|
||||||
|
|
||||||
|
public static DJObject djobj;
|
||||||
|
|
||||||
public static ChooserFrame frame;
|
public static ChooserFrame frame;
|
||||||
|
|
||||||
public static void main (String[] args)
|
public void init ()
|
||||||
{
|
{
|
||||||
boolean error = false;
|
boolean error = false;
|
||||||
|
|
||||||
@@ -85,8 +98,11 @@ public class Chooser
|
|||||||
"No music server host specified in configuration.");
|
"No music server host specified in configuration.");
|
||||||
}
|
}
|
||||||
// establish a connection with the music server
|
// establish a connection with the music server
|
||||||
scontrol = new ServerControl(
|
client = new Client(new UsernamePasswordCreds(), this);
|
||||||
host, RDJPrefs.getMusicDaemonPort());
|
client.addClientObserver(this);
|
||||||
|
client.setServer(host, Client.DEFAULT_SERVER_PORT);
|
||||||
|
client.logon();
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
String errmsg = "Unable to communicate with music " +
|
String errmsg = "Unable to communicate with music " +
|
||||||
"server on host '" + RDJPrefs.getMusicDaemonHost() + "' ";
|
"server on host '" + RDJPrefs.getMusicDaemonHost() + "' ";
|
||||||
@@ -98,12 +114,110 @@ public class Chooser
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// create our primary user interface frame, center the frame in
|
// documentation inherited from interface
|
||||||
// the screen and show it
|
public void clientDidLogon (Client lclient)
|
||||||
frame = new ChooserFrame();
|
{
|
||||||
frame.setSize(660, frame.getToolkit().getScreenSize().height - 60);
|
// subscribe to our DJObject
|
||||||
SwingUtil.centerWindow(frame);
|
DJService djsvc = (DJService)client.requireService(DJService.class);
|
||||||
frame.setVisible(true);
|
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;
|
package robodj.chooser;
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
|
|
||||||
import com.samskivert.swing.Controller;
|
import com.samskivert.swing.Controller;
|
||||||
import com.samskivert.swing.util.TaskMaster;
|
|
||||||
import com.samskivert.swing.util.TaskObserver;
|
|
||||||
|
|
||||||
import robodj.Log;
|
import robodj.Log;
|
||||||
|
|
||||||
@@ -16,12 +13,9 @@ import robodj.Log;
|
|||||||
* Handles top-level chooser UI commands.
|
* Handles top-level chooser UI commands.
|
||||||
*/
|
*/
|
||||||
public class ChooserController extends Controller
|
public class ChooserController extends Controller
|
||||||
implements TaskObserver
|
|
||||||
{
|
{
|
||||||
public ChooserController ()
|
public ChooserController ()
|
||||||
{
|
{
|
||||||
// read our playing state
|
|
||||||
TaskMaster.invokeMethodTask("refreshPlaying", Chooser.scontrol, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
@@ -29,48 +23,28 @@ public class ChooserController extends Controller
|
|||||||
{
|
{
|
||||||
String cmd = event.getActionCommand();
|
String cmd = event.getActionCommand();
|
||||||
if (cmd.equals("exit")) {
|
if (cmd.equals("exit")) {
|
||||||
System.exit(0);
|
Chooser.exit();
|
||||||
|
|
||||||
} else if (cmd.equals("skip")) {
|
} else if (cmd.equals("skip")) {
|
||||||
TaskMaster.invokeMethodTask("skip", Chooser.scontrol, this);
|
Chooser.djobj.skip();
|
||||||
|
|
||||||
} else if (cmd.equals("back")) {
|
} else if (cmd.equals("back")) {
|
||||||
TaskMaster.invokeMethodTask("back", Chooser.scontrol, this);
|
Chooser.djobj.back();
|
||||||
|
|
||||||
} else if (cmd.equals("pause")) {
|
} else if (cmd.equals("pause")) {
|
||||||
TaskMaster.invokeMethodTask("pause", Chooser.scontrol, this);
|
Chooser.djobj.pause();
|
||||||
|
|
||||||
} else if (cmd.equals("play")) {
|
} else if (cmd.equals("play")) {
|
||||||
TaskMaster.invokeMethodTask("play", Chooser.scontrol, this);
|
Chooser.djobj.play();
|
||||||
|
|
||||||
} else if (cmd.equals("stop")) {
|
} else if (cmd.equals("stop")) {
|
||||||
TaskMaster.invokeMethodTask("stop", Chooser.scontrol, this);
|
Chooser.djobj.stop();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Unknown action event: " + cmd);
|
Log.warning("Unknown action event: " + cmd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
@@ -19,17 +21,21 @@ import robodj.repository.*;
|
|||||||
import robodj.util.ButtonUtil;
|
import robodj.util.ButtonUtil;
|
||||||
import robodj.util.FancyPanel;
|
import robodj.util.FancyPanel;
|
||||||
import robodj.util.RDJPrefs;
|
import robodj.util.RDJPrefs;
|
||||||
import robodj.util.ServerControl.PlayingListener;
|
|
||||||
|
|
||||||
public class ChooserFrame extends JFrame
|
public class ChooserFrame extends JFrame
|
||||||
implements PlayingListener, ControllerProvider
|
implements ControllerProvider
|
||||||
{
|
{
|
||||||
public ChooserFrame ()
|
public ChooserFrame ()
|
||||||
{
|
{
|
||||||
super("RoboDJ Chooser " + Version.RELEASE_VERSION);
|
super("RoboDJ Chooser " + Version.RELEASE_VERSION);
|
||||||
|
|
||||||
// quit if we're closed
|
// quit gracefully if we're closed
|
||||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
||||||
|
addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosing (WindowEvent e) {
|
||||||
|
Chooser.exit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// we create a top-level panel to manage everything
|
// we create a top-level panel to manage everything
|
||||||
GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH);
|
GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH);
|
||||||
@@ -109,8 +115,8 @@ public class ChooserFrame extends JFrame
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// add ourselves as a playing listener
|
// TODO: add ourselves as a playing listener
|
||||||
Chooser.scontrol.addPlayingListener(this);
|
// Chooser.djobj.addListener(this);
|
||||||
|
|
||||||
// create our controller
|
// create our controller
|
||||||
_controller = new ChooserController();
|
_controller = new ChooserController();
|
||||||
@@ -152,6 +158,7 @@ public class ChooserFrame extends JFrame
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: convert to dobj biz
|
||||||
public void playingUpdated (int songid, boolean paused)
|
public void playingUpdated (int songid, boolean paused)
|
||||||
{
|
{
|
||||||
if (songid == -1 || paused) {
|
if (songid == -1 || paused) {
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ import javax.swing.event.AncestorListener;
|
|||||||
import com.samskivert.io.PersistenceException;
|
import com.samskivert.io.PersistenceException;
|
||||||
import com.samskivert.swing.util.SwingUtil;
|
import com.samskivert.swing.util.SwingUtil;
|
||||||
import com.samskivert.swing.util.TaskMaster;
|
import com.samskivert.swing.util.TaskMaster;
|
||||||
|
import com.samskivert.util.ArrayUtil;
|
||||||
|
|
||||||
import robodj.Log;
|
import robodj.Log;
|
||||||
|
import robodj.data.PlaylistEntry;
|
||||||
import robodj.repository.Entry;
|
import robodj.repository.Entry;
|
||||||
import robodj.repository.Song;
|
import robodj.repository.Song;
|
||||||
|
|
||||||
@@ -71,11 +73,7 @@ public abstract class EntryController extends ItemController
|
|||||||
_list.populateEntries(_entries);
|
_list.populateEntries(_entries);
|
||||||
|
|
||||||
} else if (name.equals("readAndPlay")) {
|
} else if (name.equals("readAndPlay")) {
|
||||||
for (int i = 0; i < _entry.songs.length; i++) {
|
appendToPlaylist(_entry.songs);
|
||||||
Chooser.scontrol.append(_entry.songs[i].entryid,
|
|
||||||
_entry.songs[i].songid,
|
|
||||||
_entry.songs[i].location);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (name.equals("readSongs")) {
|
} else if (name.equals("readSongs")) {
|
||||||
_list.populateSong(_entry);
|
_list.populateSong(_entry);
|
||||||
@@ -110,7 +108,7 @@ public abstract class EntryController extends ItemController
|
|||||||
|
|
||||||
} else if (cmd.equals(SongItem.PLAY)) {
|
} else if (cmd.equals(SongItem.PLAY)) {
|
||||||
Song song = SongItem.getSong(e.getSource());
|
Song song = SongItem.getSong(e.getSource());
|
||||||
Chooser.scontrol.append(song.entryid, song.songid, song.location);
|
appendToPlaylist(new Song[] { song });
|
||||||
|
|
||||||
} else if (cmd.equals("refresh")) {
|
} else if (cmd.equals("refresh")) {
|
||||||
// re-read the category
|
// re-read the category
|
||||||
@@ -164,6 +162,20 @@ public abstract class EntryController extends ItemController
|
|||||||
// nothing doing
|
// 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 EntryList _list;
|
||||||
protected Entry[] _entries;
|
protected Entry[] _entries;
|
||||||
protected Entry _entry;
|
protected Entry _entry;
|
||||||
|
|||||||
@@ -13,32 +13,42 @@ import javax.swing.JOptionPane;
|
|||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
import com.samskivert.io.PersistenceException;
|
import com.samskivert.io.PersistenceException;
|
||||||
import com.samskivert.util.ArrayUtil;
|
|
||||||
import com.samskivert.util.StringUtil;
|
import com.samskivert.util.StringUtil;
|
||||||
|
|
||||||
import com.samskivert.swing.util.SwingUtil;
|
import com.samskivert.swing.util.SwingUtil;
|
||||||
import com.samskivert.swing.util.TaskAdapter;
|
import com.samskivert.swing.util.TaskAdapter;
|
||||||
import com.samskivert.swing.util.TaskMaster;
|
import com.samskivert.swing.util.TaskMaster;
|
||||||
|
|
||||||
|
import com.threerings.presents.dobj.AttributeChangeListener;
|
||||||
|
import com.threerings.presents.dobj.AttributeChangedEvent;
|
||||||
|
|
||||||
import robodj.Log;
|
import robodj.Log;
|
||||||
import robodj.chooser.PlaylistPanel.PlaylistEntry;
|
import robodj.data.DJObject;
|
||||||
|
import robodj.data.PlaylistEntry;
|
||||||
import robodj.repository.Entry;
|
import robodj.repository.Entry;
|
||||||
import robodj.repository.Song;
|
import robodj.repository.Song;
|
||||||
import robodj.util.ServerControl.PlayingListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles playlist UI commands.
|
* Handles playlist UI commands.
|
||||||
*/
|
*/
|
||||||
public class PlaylistController extends ItemController
|
public class PlaylistController extends ItemController
|
||||||
implements PlayingListener
|
implements AttributeChangeListener
|
||||||
{
|
{
|
||||||
public PlaylistController (PlaylistPanel panel)
|
public PlaylistController (PlaylistPanel panel)
|
||||||
{
|
{
|
||||||
super(panel);
|
super(panel);
|
||||||
_panel = panel;
|
_panel = panel;
|
||||||
|
|
||||||
// add ourselves as a playing listener
|
// listen to the DJ object
|
||||||
Chooser.scontrol.addPlayingListener(this);
|
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
|
// documentation inherited
|
||||||
@@ -46,121 +56,47 @@ public class PlaylistController extends ItemController
|
|||||||
{
|
{
|
||||||
String cmd = e.getActionCommand();
|
String cmd = e.getActionCommand();
|
||||||
if (cmd.equals(PlaylistPanel.CLEAR)) {
|
if (cmd.equals(PlaylistPanel.CLEAR)) {
|
||||||
TaskMaster.invokeMethodTask("clear", Chooser.scontrol, this);
|
Chooser.djobj.clear();
|
||||||
|
|
||||||
} 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);
|
|
||||||
|
|
||||||
} else if (cmd.equals(SongItem.SKIP_TO)) {
|
} else if (cmd.equals(SongItem.SKIP_TO)) {
|
||||||
JButton button = (JButton)e.getSource();
|
JButton button = (JButton)e.getSource();
|
||||||
SongItem item = (SongItem)button.getParent();
|
SongItem item = (SongItem)button.getParent();
|
||||||
|
Chooser.djobj.play(((Integer)item.extra).intValue());
|
||||||
// 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);
|
|
||||||
|
|
||||||
} else if (cmd.equals(SongItem.REMOVE)) {
|
} else if (cmd.equals(SongItem.REMOVE)) {
|
||||||
JButton button = (JButton)e.getSource();
|
JButton button = (JButton)e.getSource();
|
||||||
SongItem item = (SongItem)button.getParent();
|
SongItem item = (SongItem)button.getParent();
|
||||||
|
int sidx = ((Integer)item.extra).intValue();
|
||||||
// remove the entry UI elements
|
Chooser.djobj.remove(sidx, 1);
|
||||||
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);
|
|
||||||
|
|
||||||
} else if (cmd.equals(PlaylistPanel.REMOVE_ALL)) {
|
} else if (cmd.equals(PlaylistPanel.REMOVE_ALL)) {
|
||||||
JButton src = (JButton)e.getSource();
|
JButton src = (JButton)e.getSource();
|
||||||
PlaylistEntry entry =
|
PlaylistPanel.PLE entry =
|
||||||
(PlaylistEntry)src.getClientProperty("entry");
|
(PlaylistPanel.PLE)src.getClientProperty("entry");
|
||||||
|
|
||||||
// remove all entries starting with this one until we get to
|
// remove all entries starting with this one until we get to
|
||||||
// one that has a different entryid
|
// one that has a different entryid
|
||||||
int count = 0;
|
int start = -1, length = 1;
|
||||||
Iterator iter = _panel.plist.iterator();
|
for (int ii = 0; ii < _panel.plist.size(); ii++) {
|
||||||
while (iter.hasNext()) {
|
PlaylistPanel.PLE pe = (PlaylistPanel.PLE)_panel.plist.get(ii);
|
||||||
PlaylistEntry pe = (PlaylistEntry)iter.next();
|
|
||||||
if (entry == pe) {
|
if (entry == pe) {
|
||||||
count++;
|
start = ii;
|
||||||
// remove the entry UI elements
|
} else if (start != -1) {
|
||||||
pe.item.getParent().remove(pe.item);
|
|
||||||
// remove the entry
|
|
||||||
iter.remove();
|
|
||||||
|
|
||||||
} else if (count > 0) {
|
|
||||||
if (pe.entry.entryid == entry.entry.entryid) {
|
if (pe.entry.entryid == entry.entry.entryid) {
|
||||||
count++;
|
length++;
|
||||||
// remove the entry UI elements
|
|
||||||
pe.item.getParent().remove(pe.item);
|
|
||||||
// remove the entry
|
|
||||||
iter.remove();
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// we hit an entry that doesn't match, bail
|
// we hit an entry that doesn't match, bail
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (start != -1) {
|
||||||
// remove the title and stuff
|
Chooser.djobj.remove(start, length);
|
||||||
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);
|
|
||||||
|
|
||||||
} else if (cmd.equals(PlaylistPanel.SHUFFLE)) {
|
} else if (cmd.equals(PlaylistPanel.SHUFFLE)) {
|
||||||
// put all of the songs in the playlist into a list and
|
// shuffle!
|
||||||
// shuffle them
|
Chooser.djobj.shuffle();
|
||||||
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);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return super.handleAction(e);
|
return super.handleAction(e);
|
||||||
@@ -169,86 +105,70 @@ public class PlaylistController extends ItemController
|
|||||||
return true;
|
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 ()
|
public void readPlaylist ()
|
||||||
throws PersistenceException
|
throws PersistenceException
|
||||||
{
|
{
|
||||||
// clear out any previous playlist
|
// clear out any previous playlist
|
||||||
_panel.plist.clear();
|
_panel.plist.clear();
|
||||||
|
|
||||||
// get the playlist from the music daemon
|
int pcount = (Chooser.djobj.playlist == null) ? 0 :
|
||||||
String[] plist = Chooser.scontrol.getPlaylist();
|
Chooser.djobj.playlist.length;
|
||||||
|
for (int ii = 0; ii < pcount; ii++) {
|
||||||
// parse it into playlist entries
|
PlaylistEntry pentry = Chooser.djobj.playlist[ii];
|
||||||
for (int i = 0; i < plist.length; i++) {
|
Entry entry = Chooser.model.getEntry(pentry.entryId);
|
||||||
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);
|
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
Song song = entry.getSong(sid);
|
Song song = entry.getSong(pentry.songId);
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
_panel.plist.add(new PlaylistEntry(entry, song));
|
_panel.plist.add(new PlaylistPanel.PLE(entry, song));
|
||||||
} else {
|
} else {
|
||||||
Log.warning("No song for entry? [sid=" + sid +
|
Log.warning("No song for entry? [pentry=" + pentry + "].");
|
||||||
", entry=" + entry + "].");
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// unhighlight whoever is playing now
|
||||||
PlaylistEntry pentry = getPlayingEntry();
|
if (_playing != null) {
|
||||||
if (pentry != null && pentry.item != null) {
|
_playing.item.setIsPlaying(false);
|
||||||
pentry.item.setIsPlaying(false);
|
_playing = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab the new playing song id
|
int pidx = Chooser.djobj.playing;
|
||||||
_playid = songid;
|
if (pidx >= 0 && _panel.plist.size() > pidx) {
|
||||||
|
_playing = (PlaylistPanel.PLE)_panel.plist.get(pidx);
|
||||||
|
}
|
||||||
|
|
||||||
// highlight the playing song
|
if (_playing != null) {
|
||||||
for (int i = 0; i < _panel.plist.size(); i++) {
|
_playing.item.setIsPlaying(true);
|
||||||
PlaylistEntry entry = (PlaylistEntry)_panel.plist.get(i);
|
|
||||||
if (entry.song.songid == _playid && entry.item != null) {
|
|
||||||
entry.item.setIsPlaying(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_panel.repaint();
|
_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)
|
public void taskCompleted (String name, Object result)
|
||||||
{
|
{
|
||||||
if (name.equals("readPlaylist")) {
|
if (name.equals("readPlaylist")) {
|
||||||
_panel.populatePlaylist(_panel.plist, _playid);
|
_panel.populatePlaylist(_panel.plist);
|
||||||
|
playingUpdated();
|
||||||
} else {
|
} else {
|
||||||
super.taskCompleted(name, result);
|
super.taskCompleted(name, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PlaylistPanel _panel;
|
protected PlaylistPanel _panel;
|
||||||
|
protected PlaylistPanel.PLE _playing;
|
||||||
protected int _playid;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import robodj.util.ButtonUtil;
|
|||||||
|
|
||||||
public class PlaylistPanel extends ControlledPanel
|
public class PlaylistPanel extends ControlledPanel
|
||||||
{
|
{
|
||||||
public static class PlaylistEntry
|
public static class PLE
|
||||||
{
|
{
|
||||||
public Entry entry;
|
public Entry entry;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ public class PlaylistPanel extends ControlledPanel
|
|||||||
|
|
||||||
public SongItem item;
|
public SongItem item;
|
||||||
|
|
||||||
public PlaylistEntry (Entry entry, Song song)
|
public PLE (Entry entry, Song song)
|
||||||
{
|
{
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
this.song = song;
|
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 CLEAR = "clear";
|
||||||
|
|
||||||
public static final String REMOVE_ALL = "remove_all";
|
public static final String REMOVE_ALL = "remove_all";
|
||||||
@@ -80,8 +78,6 @@ public class PlaylistPanel extends ControlledPanel
|
|||||||
_clearbut = ButtonUtil.createControlButton(
|
_clearbut = ButtonUtil.createControlButton(
|
||||||
CLEAR_TIP, CLEAR, CLEAR_ICON_PATH);
|
CLEAR_TIP, CLEAR, CLEAR_ICON_PATH);
|
||||||
cbar.add(_clearbut);
|
cbar.add(_clearbut);
|
||||||
cbar.add(ButtonUtil.createControlButton(
|
|
||||||
REFRESH_TIP, REFRESH, REFRESH_ICON_PATH));
|
|
||||||
cbar.add(ButtonUtil.createControlButton(
|
cbar.add(ButtonUtil.createControlButton(
|
||||||
SHUFFLE_TIP, SHUFFLE, SHUFFLE_ICON_PATH));
|
SHUFFLE_TIP, SHUFFLE, SHUFFLE_ICON_PATH));
|
||||||
add(cbar, GroupLayout.FIXED);
|
add(cbar, GroupLayout.FIXED);
|
||||||
@@ -97,8 +93,6 @@ public class PlaylistPanel extends ControlledPanel
|
|||||||
public void addNotify ()
|
public void addNotify ()
|
||||||
{
|
{
|
||||||
super.addNotify();
|
super.addNotify();
|
||||||
// load up the playlist
|
|
||||||
_controller.postAction(this, REFRESH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
@@ -118,7 +112,7 @@ public class PlaylistPanel extends ControlledPanel
|
|||||||
_bpanel.revalidate();
|
_bpanel.revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void populatePlaylist (ArrayList plist, int playid)
|
protected void populatePlaylist (ArrayList plist)
|
||||||
{
|
{
|
||||||
// clear out any existing children
|
// clear out any existing children
|
||||||
_bpanel.removeAll();
|
_bpanel.removeAll();
|
||||||
@@ -128,10 +122,12 @@ public class PlaylistPanel extends ControlledPanel
|
|||||||
gl.setOffAxisPolicy(GroupLayout.EQUALIZE);
|
gl.setOffAxisPolicy(GroupLayout.EQUALIZE);
|
||||||
gl.setOffAxisJustification(GroupLayout.LEFT);
|
gl.setOffAxisJustification(GroupLayout.LEFT);
|
||||||
|
|
||||||
|
int current = Chooser.djobj.playing;
|
||||||
|
|
||||||
// add buttons for every entry
|
// add buttons for every entry
|
||||||
String title = null;
|
String title = null;
|
||||||
for (int i = 0; i < plist.size(); i++) {
|
for (int ii = 0; ii < plist.size(); ii++) {
|
||||||
PlaylistEntry entry = (PlaylistEntry)plist.get(i);
|
PLE entry = (PLE)plist.get(ii);
|
||||||
JButton button;
|
JButton button;
|
||||||
|
|
||||||
// add record/artist indicators when the record and artist
|
// 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
|
// create a song item to display this playlist entry
|
||||||
entry.item = new SongItem(entry.song, SongItem.PLAYLIST);
|
entry.item = new SongItem(entry.song, SongItem.PLAYLIST);
|
||||||
entry.item.setIsPlaying(entry.song.songid == playid);
|
entry.item.setIsPlaying(current == ii);
|
||||||
entry.item.extra = entry;
|
entry.item.extra = new Integer(ii);
|
||||||
_bpanel.add(entry.item);
|
_bpanel.add(entry.item);
|
||||||
|
|
||||||
// let the bpanel know that we want to scroll the active track
|
// let the bpanel know that we want to scroll the active track
|
||||||
// label into place once we're all laid out
|
// label into place once we're all laid out
|
||||||
if (entry.song.songid == playid) {
|
if (current == ii) {
|
||||||
_bpanel.setScrollTarget(entry.item);
|
_bpanel.setScrollTarget(entry.item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,7 +225,6 @@ public class PlaylistPanel extends ControlledPanel
|
|||||||
|
|
||||||
// icon paths
|
// icon paths
|
||||||
protected static final String ICON_ROOT = "/robodj/chooser/images/";
|
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 CLEAR_ICON_PATH = ICON_ROOT + "clear.png";
|
||||||
protected static final String SHUFFLE_ICON_PATH = ICON_ROOT + "shuffle.png";
|
protected static final String SHUFFLE_ICON_PATH = ICON_ROOT + "shuffle.png";
|
||||||
protected static final String SKIPTO_ICON_PATH = ICON_ROOT + "skip.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";
|
ICON_ROOT + "remove_song.png";
|
||||||
|
|
||||||
// button tips
|
// button tips
|
||||||
protected static final String REFRESH_TIP = "Refresh the playlist";
|
|
||||||
protected static final String CLEAR_TIP =
|
protected static final String CLEAR_TIP =
|
||||||
"Clear all songs from the playlist";
|
"Clear all songs from the playlist";
|
||||||
protected static final String SHUFFLE_TIP =
|
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
|
extends ImporterPanel
|
||||||
implements ActionListener
|
implements ActionListener
|
||||||
{
|
{
|
||||||
public FinishedPanel (Entry entry)
|
public FinishedPanel ()
|
||||||
{
|
{
|
||||||
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
|
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
|
||||||
gl.setOffAxisPolicy(GroupLayout.STRETCH);
|
gl.setOffAxisPolicy(GroupLayout.STRETCH);
|
||||||
@@ -35,7 +35,7 @@ public class FinishedPanel
|
|||||||
|
|
||||||
// create our action buttons
|
// create our action buttons
|
||||||
String[] labels = new String[]
|
String[] labels = new String[]
|
||||||
{ "Import another CD", "Edit/categorize CDs" };
|
{ "Import more music", "Edit/categorize CDs" };
|
||||||
String[] actions = new String[] { "import", "edit" };
|
String[] actions = new String[] { "import", "edit" };
|
||||||
|
|
||||||
for (int i = 0; i < labels.length; i++) {
|
for (int i = 0; i < labels.length; i++) {
|
||||||
@@ -47,9 +47,6 @@ public class FinishedPanel
|
|||||||
|
|
||||||
// add our panel to the main group
|
// add our panel to the main group
|
||||||
add(ppanel);
|
add(ppanel);
|
||||||
|
|
||||||
// save this guy for later
|
|
||||||
_entry = entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
|
public void wasAddedToFrame (ImporterFrame frame, boolean popped)
|
||||||
@@ -68,7 +65,7 @@ public class FinishedPanel
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
|
|
||||||
} else if (cmd.equals("import")) {
|
} else if (cmd.equals("import")) {
|
||||||
_frame.setPanel(new InsertCDPanel());
|
_frame.setPanel(new StartPanel());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Unknown action event: " + cmd);
|
System.out.println("Unknown action event: " + cmd);
|
||||||
@@ -76,5 +73,4 @@ public class FinishedPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected ImporterFrame _frame;
|
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
|
// create our frame and first panel
|
||||||
ImporterFrame frame = new ImporterFrame();
|
ImporterFrame frame = new ImporterFrame();
|
||||||
InsertCDPanel panel = new InsertCDPanel();
|
frame.pushPanel(new StartPanel());
|
||||||
frame.pushPanel(panel);
|
|
||||||
|
|
||||||
// center the frame in the screen and show it
|
// center the frame in the screen and show it
|
||||||
frame.setSize(640, 480);
|
frame.setSize(1024, 600);
|
||||||
SwingUtil.centerWindow(frame);
|
SwingUtil.centerWindow(frame);
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,28 @@
|
|||||||
|
|
||||||
package robodj.importer;
|
package robodj.importer;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
import java.awt.LayoutManager;
|
import java.awt.LayoutManager;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
public abstract class ImporterPanel extends JPanel
|
public abstract class ImporterPanel extends JPanel
|
||||||
{
|
{
|
||||||
public ImporterPanel ()
|
public ImporterPanel ()
|
||||||
{
|
{
|
||||||
|
this(new BorderLayout());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImporterPanel (LayoutManager layout)
|
public ImporterPanel (LayoutManager layout)
|
||||||
{
|
{
|
||||||
super(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.
|
* than added to the frame for the first time.
|
||||||
*/
|
*/
|
||||||
public abstract void wasAddedToFrame (ImporterFrame frame, boolean popped);
|
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
|
// create the status log
|
||||||
JLabel sl = new JLabel("Status", JLabel.LEFT);
|
JLabel sl = new JLabel("Status", JLabel.LEFT);
|
||||||
ppanel.add(sl, GroupLayout.FIXED);
|
ppanel.add(sl, GroupLayout.FIXED);
|
||||||
_statusLog = new JTextArea();
|
|
||||||
_statusLog.setLineWrap(true);
|
|
||||||
_statusLog.setEditable(false);
|
|
||||||
// make something for it to scroll around in
|
// 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 our panel to the main group
|
||||||
add(ppanel);
|
add(ppanel);
|
||||||
@@ -74,16 +71,6 @@ public class RipPanel
|
|||||||
_entry = entry;
|
_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,
|
protected void postAsyncProgress (final int overallComplete,
|
||||||
final int taskComplete)
|
final int taskComplete)
|
||||||
{
|
{
|
||||||
@@ -390,7 +377,7 @@ public class RipPanel
|
|||||||
} else {
|
} else {
|
||||||
msg = exception.toString();
|
msg = exception.toString();
|
||||||
}
|
}
|
||||||
_statusLog.append("Error: " + msg + "\n");
|
_status.append("Error: " + msg + "\n");
|
||||||
Log.logStackTrace(exception);
|
Log.logStackTrace(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +388,7 @@ public class RipPanel
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
|
|
||||||
} else if (cmd.equals("next")) {
|
} else if (cmd.equals("next")) {
|
||||||
_frame.pushPanel(new FinishedPanel(_entry));
|
_frame.pushPanel(new FinishedPanel());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Unknown action event: " + cmd);
|
System.out.println("Unknown action event: " + cmd);
|
||||||
@@ -424,7 +411,6 @@ public class RipPanel
|
|||||||
|
|
||||||
protected JProgressBar _oprogress;
|
protected JProgressBar _oprogress;
|
||||||
protected JProgressBar _progress;
|
protected JProgressBar _progress;
|
||||||
protected JTextArea _statusLog;
|
|
||||||
|
|
||||||
protected Ripper.TrackInfo[] _info;
|
protected Ripper.TrackInfo[] _info;
|
||||||
protected Entry _entry;
|
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;
|
package robodj.importer;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
public class InsertCDPanel
|
import com.samskivert.swing.VGroupLayout;
|
||||||
extends ImporterPanel
|
|
||||||
|
public class StartPanel extends ImporterPanel
|
||||||
implements ActionListener
|
implements ActionListener
|
||||||
{
|
{
|
||||||
public InsertCDPanel ()
|
public StartPanel ()
|
||||||
{
|
{
|
||||||
super(new GridLayout(1, 2));
|
super(new GridLayout(1, 2));
|
||||||
|
|
||||||
@@ -24,10 +27,21 @@ public class InsertCDPanel
|
|||||||
JLabel cdlabel = new JLabel(cdicon);
|
JLabel cdlabel = new JLabel(cdicon);
|
||||||
add(cdlabel);
|
add(cdlabel);
|
||||||
|
|
||||||
// create the "insert cd" text label for the right hand side
|
// allow the user to select whether they want to rip a CD or
|
||||||
JLabel ilabel = new JLabel("Please insert the CD...",
|
// import music directly
|
||||||
JLabel.CENTER);
|
VGroupLayout vgl = new VGroupLayout();
|
||||||
add(ilabel);
|
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
|
// give ourselves a wee bit of a border
|
||||||
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||||
@@ -49,12 +63,17 @@ public class InsertCDPanel
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
|
|
||||||
} else if (cmd.equals("next")) {
|
} else if (cmd.equals("next")) {
|
||||||
_frame.pushPanel(new CDDBLookupPanel());
|
if (_rip.isSelected()) {
|
||||||
|
_frame.pushPanel(new CDDBLookupPanel());
|
||||||
|
} else {
|
||||||
|
_frame.pushPanel(new ImportMusicPanel());
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Unknown action event: " + cmd);
|
System.out.println("Unknown action event: " + cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected JRadioButton _imp, _rip;
|
||||||
protected ImporterFrame _frame;
|
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;
|
protected String _prefKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main (String[] args)
|
||||||
|
{
|
||||||
|
display(true);
|
||||||
|
}
|
||||||
|
|
||||||
protected JFrame _frame;
|
protected JFrame _frame;
|
||||||
|
|
||||||
/** Defines our configurable preferences. */
|
/** Defines our configurable preferences. */
|
||||||
|
|||||||
Reference in New Issue
Block a user