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.

comments (17) | write a comment | permalink | 13 December 2004

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.