Using the iTunes COM Interface with Java and Swing

Last month, I wrote a blog entry which described how to use Java to communicate with the iTunes COM interface for Windows. Even though the entry was just a very quick “look, you can do this” sort of piece, it received high quality links and a fair bit of traffic from some good sites. From these, I got feedback from a few people saying thank you for helping them out by making the code available, but that (amongst other things), they couldn’t get it working with Swing. That night, I hacked together a version of the code that worked with Swing.

Before we begin

As in the previous blog entry, we’re going to be using JACOB to communicate with the iTunes COM interface. JACOB is essentially a port of the Microsoft Java to COM interface which was written to work with the Microsoft JVM. Obviously, with the death of the MIcrosoft JVM and the fact that it was never very good to begin with, there was a need for a version which would work with the standard Sun JVM. And so, JACOB was born.

In order to run the example in this article, you’ll need to download the latest JACOB package (any version 1.8+ should be acceptable). I’ll assume for this example that you’ve put the jacob.dll and jacob.jar files in the same directory as the example class which we’ll create later.

As well as the JACOB run-times, you’ll also need the API documentation if you’re going to be able to do anything further than what you’re shown in this article. Unfortunately, the JACOB project does not have any useable API documentation yet. Thankfully, since it’s so closely based on the original Microsoft implementation, this isn’t really a problem, since the Microsoft documentation is plenty good enough. Unfortunately, it isn’t that easy to get hold of. There is one place you can go to get hold of it though - the JACOB Yahoo Group. Sign up to the group using your Yahoo ID, and go to the Files section. Once you’re there, download the sdkdocs.zip.aa and sdkdocs.zip.aa files, and concatenate them to make one single sdkdocs.zip file. You can concatenate them by opening a command window in the directory you saved the files in, and typing type sdkdocs.zip.aa sdkdocs.zip.ab > sdkdocs.zip. All being well, you’ll be able to unzip sdkdocs.zip, and open the resulting files using Microsoft’s Help Viewer.

Assuming you haven’t already downloaded it, now would be a good time to grab the iTunes COM interface SDK.

Now, as long as you’ve already got Sun’s Java JDK installed, we’re all set to go.

Getting Started

First, lets do a very quick test to make sure that we can get a Java application communicating with iTunes. This is basically a little cut down version of the code which I wrote in the previous blog entry. Save the following as ITunesTest1.java:

import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.activeX.ActiveXComponent;

public class ITunesTest1 {

	public ITunesTest1() {
		ComThread.InitMTA(true);

		ActiveXComponent iTunesCom = new ActiveXComponent("iTunes.Application");

		Dispatch iTunesController = (Dispatch)iTunesCom.getObject();

		Dispatch.call(iTunesController, "PlayPause");

		ComThread.Release();

		System.exit(0);
	}


	public static void main(String args[]) throws Exception {
		ITunesTest1 test = new ITunesTest1();
	}

}

All that code does is set up a ComThread, attaches itself to your already running instance of iTunes, and toggles whether it’s playing or not. It then releases the ComThread that it set up and exits the application. Nice and simple. To run the test, simply open a command window in the directory that you’ve got the ITunesTest1.java file in.

javac -cp ./jacob.jar;. ITunesTest1.java
java -cp ./jacob.jar;. ITunesTest1

All being well, when you hit Enter after inputting the second line, you’ll be greeted by iTunes toggling whether or not it’s playing. So, if you weren’t playing anything when you ran the test then iTunes should errupt into a flurry of song, and vice versa. If this doesn’t happen, then check that you’ve got the JACOB files in the right place, and that you’re using iTunes on a Windows platform!

Listening for Events

Now that we know that we can send information to Itunes, lets go the other way and receive information back from it. We do this using JACOB’s DispatchEvents class. The constructor for this class takes two parameters - the Dispatch instance for the application that we’re hooked into, and an “event class” which contains methods related to the events which are generated by the application that we’re hooked into. This “event class” contains one method for each event which is generated by the application that you want to monitor. Each method has the same name as the generated event, takes a single array of type Variant as a parameter, and does not return anything. iTunes has various events which it will generate, but for this example, we only want to monitor OnPlayerPlayEvent.

To do this in a single threaded application, we use the following code (the red lines are new compared to last time):

import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.DispatchEvents;
import com.jacob.com.Variant;
import java.io.IOException;

public class ITunesTest2 {

	public ITunesTest2() {
		ComThread.InitMTA(true);

		ActiveXComponent iTunesCom = new ActiveXComponent("iTunes.Application");

		Dispatch iTunesController = (Dispatch)iTunesCom.getObject();

		DispatchEvents events = new DispatchEvents(iTunesController, new ITunesEvents());

		try {
			System.in.read();
		} catch (IOException e) {}

		ComThread.Release();

		System.exit(0);
	}

	private class ITunesEvents {
		public void OnPlayerPlayEvent(Variant[] args) {
			System.out.println("OnPlayerPlayEvent");
		}
	}

	public static void main(String args[]) throws Exception {
		ITunesTest2 test = new ITunesTest2();
	}

}

When you compile and run the class, you’ll be informed every time a song starts playing on iTunes. Press Enter to terminate the application. Make sure you terminate it this way, as if the ComThread does not get released, then iTunes will think that you still have a handle to it and will not let you watch for events if you ran the application again. The only way I know to let iTunes let you listen again if you don’t close the application correctly is to restart iTunes itself.

Getting it to work with Swing

Unfortunately, things don’t work quite this easily with Swing, and this is the real reason that I’ve written this article. If you try to do things this simplistically and use Swing as well, then the ComThread does not get released successfully, and you have to restart iTunes before you can run your Swing application again (receiving a warning message from iTunes in the process).

The solution to the problem when using Swing is to run the JACOB code in a separate Thread, and not allow the main application Thread to shut down until the JACOB Thread has completed all its business. This is fairly easy, but it took me a while to work out that this was the way that things should be done.

The following code is an example of what you can use to get a Swing application talking to iTunes before successfully unattaching itself from iTunes before it closes. Once again, the notable code additions are highlighted.

import com.jacob.com.*;
import javax.swing.*;
import java.awt.event.*;
import java.io.IOException;
import java.awt.BorderLayout;
import com.jacob.activeX.ActiveXComponent;

public class ITunesTest3 extends JFrame {

	private ITunesThread iTunes;

	public ITunesTest3() {
		JButton btnExit = new JButton("Exit");
		btnExit.addActionListener(new ExitListener());

		this.getContentPane().setLayout(new BorderLayout());
		this.getContentPane().add(btnExit, BorderLayout.CENTER);
		this.addWindowListener(new ExitListener());
		this.pack();

		iTunes = new ITunesThread();
		iTunes.start();
	}

	public void doExit() {
		iTunes.quit = true;
		while (!iTunes.finished) {
			synchronized(this) {
				try {
					System.out.println("Waiting for the ITunesThread to finish");
					wait(100);
				} catch (InterruptedException e) {}
			}
		}
		System.exit(0);
	}

	private class ExitListener implements ActionListener, WindowListener {
		public void actionPerformed(ActionEvent evt) {
			doExit();
		}
		
		public void windowClosing(WindowEvent e) {
			doExit();
		}

		public void windowActivated(WindowEvent e) {}
		public void windowClosed(WindowEvent e) {}
		public void windowDeactivated(WindowEvent e) {}
		public void windowDeiconified(WindowEvent e) {}
		public void windowIconified(WindowEvent e) {}
		public void windowOpened(WindowEvent e) {}
	}

	private class ITunesThread extends Thread {
		public boolean quit = false;
		public boolean finished = false;

		public void run () {
			ComThread.InitMTA(true);

			ActiveXComponent iTunesCom = new ActiveXComponent("iTunes.Application");

			Dispatch iTunesController = (Dispatch)iTunesCom.getObject();

			DispatchEvents events = new DispatchEvents(iTunesController, new ITunesEvents());

			while (!quit) {
				try {
					sleep(100);
				} catch (InterruptedException e) {}
			}

			ComThread.Release();

			finished = true;
			System.out.println("ITunesThread has finished");
		}
	}

	private class ITunesEvents {
		public void OnPlayerPlayEvent(Variant[] args) {
			System.out.println("OnPlayerPlayEvent");
		}
	}

	public static void main(String[] args) {
		ITunesTest3 app = new ITunesTest3();
		app.setVisible(true);
	}
}

As you can see, the main body of code from the previous examples has now been moved into its own ITunesThread, which continually sleeps until someone sets its public quit variable to true. At this point, the ITunesThread releases the ComThread, and sets its finished variable to true, signifying that the Swing application is now allowed to exit.

The code which controls this ending of the ITunesThread is found in the Swing application’s doExit() method. It changes the value of the ITunesThread’s quit variable to true and then waits until the ITunesThread’s finished variable reads true. This “does the job”, and the application successfully finishes after releasing the ComThread.

Making it all do something useful

Of course, nothing that we’ve done here actually does anything useful, so I’m going to finish with a little snippet to add to the OnPlayerPlayEvent method which will output the song title and artist to STDOUT whenever a song is played. Getting the application to do anything more elaborate is an exercise which shall be left to the reader.

public void OnPlayerPlayEvent(Variant[] args) {
	System.out.println("OnPlayerPlayEvent");

	Dispatch event = (Dispatch)args[0].getDispatch();

	System.out.println("Artist: " + Dispatch.get(event, "Artist"));
	System.out.println("Album: " + Dispatch.get(event, "Album"));
	System.out.println("Name: " + Dispatch.get(event, "Name"));
}

So, there you go. That’s pretty much the basics covered so that you can get Java successfully communicating with the iTunes COM interface. It’s all pretty groovy, and something that I’m hoping to explore further at a later date.

If you enjoyed reading this and would like other people to read it as well, please add it to del.icio.us, digg or furl.

If you really enjoyed what you just read, why not buy yourself something from Amazon? You get something nice for yourself, and I get a little bit of commission to pay for servers and the like. Everyone's a winner!

comments (17) | write a comment | permalink | View blog reactions

Comments

  1. by Sylvain on December 31, 2004 02:13 AM

    Hi,

    I have been reading your posting about the Itunes SDK for Windows with interest.

    Indeed, I would like to create a simple HTML interface to Itunes so I can remotely command Itunes from a pocket PC device within my appartment (list playlists, play, stop play list, next track…)

    Programming wise, I can easily find my way with HTML, ASP, VBscript, web servers configuration but I have never used PERL, Java or developped dll using C and a COM interface of some sort.

    From everything I have been reading about the Itunes SDK, there is nothing available that I could re-use into a VBscript. It seems that I need to either learn about perl or dig into some souvenirs or C programming and figure out how to create a dll, register it and then use it into an ASP page.

    Any suggestion of an easier path would be appreciated.

    Thanks in advance for your answer and Happy New Year.

  2. by David Lowe on March 23, 2005 09:46 AM

    hi,

    i just finished working through your examples and i have to say it was great. really helpful well explained and most of all, it works. thanks for this, it gives me a launch pad to create the apps i have in mind.

    thank you for taking the time to do this

  3. by Scott on October 11, 2005 03:25 AM

    Does iTunes on OS X expose a COM (or COM-like) API that can be used the same way?

    Thanks

  4. by Neil Crosby [TypeKey Profile Page] on October 11, 2005 08:46 AM

    On OS X you can use AppleScript to control iTunes (and whole host of other stuff). Just one place you could look for pointers with that is http://www.dougscripts.com/itunes/

  5. by Col on October 16, 2005 03:26 PM

    Hi, Thanks for your great article. It has helped me a lot. I have been able to do alot with the information you provided but one area is still causing me much confusion. Can you please give me some advise on how I can go about getting some information about my music library back from iTunes via the Jacob COM bridge?

    I know how to call all the simple commands and even pass values to iTunes (such as setting the volume etc.) but how to I manage to get info back? Is there a way to implement the the data object interfaces such as the IITrackCollection or IITrackPlaylist Interface in Java?

    It would be great to hear any advise anyone can give me on this matter.

    cheers

  6. by Anonymous on October 28, 2005 08:07 PM

    I got curious and found a way to do this. First off, go to Sourceforge and get the latest JACOB and JacobGen. JacobGen is the app you need for this. In the docs directory you will find a file called runjacobgen.bat. Edit this to fit your machine. You will need to set JAVAHOME and such to match your setup. Then run it. I’ll provide what you need below. This will generate a set of Java classes for working with iTunes on Windows via the COM interface. It’s slick as hell. Make sure to put the jacob.dll somewhere Windows can find it. I just dumped it in C:\windows\system32.

    run_jacobgen.bat:

    @echo off
    cls
    REM run this from the root directory of the Jacobgen project
    REM it will spit out the interface classes for a dll you pass in as a parameter
    REM sample command line while sitting in the JACOBGEN project directory
    REM
    REM The following command built a sample in the jacob directory I have
    REM installed near my jacobgen proejct directory
    REM $ docs/run_jacobgen.bat -destdir:"..\jacob\samples" -listfile:"jacobgenlog.txt" -package:com.jacobgen.microsoft.msword "C:\Program Files\Microsoft Office\OFFICE11\MSWORD.OLB"
    REM
    REM
    set JAVA_HOME=C:\Java\j2sdk1.4.2_09
    set JRE=%JAVA_HOME%\bin\java
    set JACOBGEN_HOME=.
    set CLASSPATH=%CLASSPATH%;%JAVA_HOME%\lib\dt.jar;%JACOBGEN_HOME%\jacobgen.jar;%JACOBGEN_HOME%\samskivert.jar
    REM put the dll in the path where we can find it
    set PATH=%PATH%;%JACOBGEN_HOME%\release
    rem echo %CLASSPATH%
    %JRE% -Xint com.jacob.jacobgen.Jacobgen %1 %2 %3 %4 %5
    pause

    Command to run it with:

    docs\run_jacobgen.bat -destdir:"C:\Java\jacobgen_0.5\samples" -listfile:"jacobgenlog.txt" -package:com.apple.itunes "C:\Program Files\iTunes\iTunes.exe"

    (That should all be one line. Again, make sure the paths in the command are correct.)

    Now you have a Java package tree in C:\Java\jacobgen_0.5\samples. You will need to include these in your Java project/classpath. I use NetBeans to setup the project, so I just cut-and-pasted the “com” directory into my src dir for the project. Netbeans picked it up and added it. You’re on your own for setting that up. Here’s some sample source that lists the playlists. You can go from there to do all sorts of stuff. It’s just a slightly modified version of the “simple” code above.

    import com.jacob.com.ComThread;
    import com.jacob.com.Dispatch;
    import com.jacob.activeX.ActiveXComponent;
    import com.apple.itunes.*;
    public class Main {
    public Main() {
    ComThread.InitMTA(true);
    ActiveXComponent iTunesCom = new ActiveXComponent("iTunes.Application");
    Dispatch iTunesController = (Dispatch)iTunesCom.getObject();
    IiTunes it = new IiTunes(iTunesController);
    IITSourceCollection sourceList = it.getSources();
    for (int i = 1; i <= sourceList.getCount(); i++)
    {
    IITSource s = sourceList.getItem(i);
    System.out.println(s.getName());
    IITPlaylistCollection lists = s.getPlaylists();
    for (int j = 1; j <= lists.getCount(); j++)
    {
    IITPlaylist pl = lists.getItem(j);
    System.out.println(pl.getName());
    }
    }
    // Dispatch.call(iTunesController, "PlayPause");
    ComThread.Release();
    System.exit(0);
    }
    public static void main(String args[]) throws Exception {
    Main test = new Main();
    }
    }

    If you can’t get jacobgen to work, I’ll consider posting the source tree it generates online somewhere. I doubt apple would care, it’s free advertising for them… I’ll just check back on here once in a while…

  7. by Anonymous on November 9, 2005 01:59 AM

    Control iTunes from a Servlet

    Hello, I could follow your example and Play/Pause iTunes using Jacob 1.9 and j2sdk1.4.2_09. but I could not make it work in a servlet running in Tomcat 5.0.28. I get the following error:

    ----- Root Cause ----- 
    com.jacob.com.ComFailException: Can't co-create object 
    at com.jacob.com.Dispatch.createInstance(Native Method) 
    at com.jacob.com.Dispatch.(Dispatch.java:146) 
    at com.jacob.activeX.ActiveXComponent.(ActiveXComponent.java:58) 
    at example.sum.ITunesCOM.(ITunesCOM.java:19) 

    … I stored jacob.dll in c:\WINDOWS\system32. Do you know how I could make it work? Thanks!

  8. by Anonymous on January 11, 2006 08:32 AM

    jacob 1.10-pre2 didn’t work with the example as listed. I tried 1.8 and it worked just fine. The error said something about only public classes could ????? So I think that the devs on the project have changed the ?scope? of one of the classes. I didn’t try any of the versions in between so who knows about when it was changed. I could look into it more but I’m heading off to bed. :-) Thanks for the great example btw. It seems so hard to find any good windows example code of using the iTunes com. Of course the com has certain limitations… Does it break anything with apple if you create an app that directly edits the library xml file? I’d like to create some apps for better file management. Apples automatic one is great but sucks if your mp3s only information is in the file name! The com doesn’t seem to suport changeing the location. Of course I could write an app that could move the file and then edit the xml directly… I wonder if Apple would frown on that? It looks like some people have created mac scripts to do stuff like that. Well I’m tossing around the idea of creating an open source iTunes tools app. If anyone else is interested post here. I’ll try to remember to check back and post the possible SourceForge site. (Probably after I get some kind of base code written.) — AS

  9. by Anonymous on January 11, 2006 08:35 AM

    oh my Sourceforge name is fuzzyfonzy if anybody wants to contact me concerning an opensource project. (Note that if I’m not on one 3 months from the date of post I probably decided not to start one :-) )

    — AS

  10. by Anonymous on February 2, 2006 01:58 AM

    I have jacob 1.10-pre2 and got the same error. I simply fixed it by taking: private class ITunesEvents and making it public inside its own file. Apparently, the DispatchEvents() method now needs a public class as its second argument.

  11. by Anonymous on March 9, 2006 08:10 PM

    I programmed a little client/server app that uses this exact same principle. It uses XML data over sockets to communicate with multiple clients and uses Jacob for making the COM calls to iTunes. If you want to lend me a hand, the source code is available on sourceforge - http://itunesanywhere.sourceforge.net/

  12. by Anonymous on April 9, 2006 02:23 PM

    Hi,

    I am currently using the iTunes COM object with VB.NET. I have successfully set up the event handlers and simple control + extract the current playlist, however, I don’t seem to be able to select tracks from a playlist directly. Is it true that this is not possible with the iTunes COM interface?

  13. by slim on July 24, 2006 07:32 PM

    i have many resources that might help you at http://www.itunessdk.com/blog

  14. by Mitchum on August 24, 2006 05:42 PM

    Hey guys,…

    Its easier for Mac users, but if you’re a windows user and want to play a particular podcast as an alarm, I’ve created a script to do it pretty easily…

    http://www.mitchumowen.com/downloads/alarm.js

    The instructions are in the file. To edit the preferences, you need to open the file with notepad or other editor.

    Enjoy!

  15. by Hui on January 30, 2007 10:19 PM

    I got some problems when I tried to generate a set of java class files to “C:\Java\jacobgen_0.5\samples”. Did anyone successfully generate those files? It would be appreciated, if anyone can provide a link to download these files directly. Thanks a lot!!

  16. by Technika on April 18, 2007 11:02 AM

    Mitchum - Thank You. It be very helpful for me :)

  17. by Marko Novakovic on July 4, 2007 12:27 PM

    Update:

    This is for Java 1.5 version and new jacobgen

    @echo off cls REM run this from the root directory of the Jacobgen project REM it will spit out the interface classes for a dll you pass in as a parameter REM sample command line while sitting in the JACOBGEN project directory REM REM The following command built a sample in the jacob directory I have REM installed near my jacobgen proejct directory REM $ docs/runjacobgen.bat -destdir:”..\jacob\samples” -listfile:”jacobgenlog.txt” -package:com.jacobgen.microsoft.msword “C:\Program Files\Microsoft Office\OFFICE11\MSWORD.OLB” REM REM set JAVAHOME=”c:\Program Files\Java\jdk1.5.006" set JRE=%JAVAHOME%\bin\java set JACOBGENHOME=. set CLASSPATH=%CLASSPATH%;%JAVAHOME%\lib\dt.jar;%JACOBGENHOME%\jacobgen.jar;%JACOBGENHOME%\viztool.jar REM put the dll in the path where we can find it set PATH=%PATH%;%JACOBGEN_HOME%\release rem echo %CLASSPATH% %JRE% -Xint com.jacob.jacobgen.Jacobgen %1 %2 %3 %4 %5 pause

    batch file: docs\runjacobgen.bat -destdir:”C:\Java\jacobgen0.5\samples” -listfile:”jacobgenlog.txt” -package:com.apple.itunes “C:\Program Files\iTunes\iTunes.exe”

other relevant pages

about wwm

workingwith.me.uk is a resource for web developers created by Neil Crosby, a web developer who lives and works in London, England. More about the site.

Neil Crosby now blogs at The Code Train and also runs NeilCrosby.com, The Ten Word Review and Everything is Rubbish.