QTJ Audio
by Chris Adamson12/17/2003
QuickTime Java can be the heart and soul of cross-platform video players and editors. As you will see in this article, QTJ is also well-suited to be the engine of audio-only applications, such as MP3 players. This article will develop an audio player, QTBebop, that displays song metadata, band levels, and current time, all of which help introduce the useful audio-related tools provided by QuickTime to the Java developer. We'll also look at QuickTime's "callbacks," which are critical to all kinds of QT apps.
Too Good, Too Bad
We tend to think of audio and video applications as separate realms
— iTunes and WinAmp are one kind of application, while
iMovie and RealPlayer are another — but this separation exists
at the application layer, not the media framework layer. QuickTime
treats sound the same way it treats video or any other kind of dynamic media. In fact, there's nothing special
to opening and playing a sound file in QuickTime: you create an
OpenMovieFile to reference a file in a supported format
(MP3, AAC, WAV, etc.), hand that to Movie.fromFile(), and
call Movie.start(). The qtj61-player code
from the last article in this series will play audio files with no code changes. As far as QuickTime is concerned, imported audio files are just movies
with a single track of audio media.
Given that, it's quite easy to write a bare-bones, GUI-less audio player. In fact, it seems like it should consist of simply opening a file, making a Movie from that, and starting the
Movie. We could express this as the following code with only
a single caveat ... it doesn't work:
try {
//this does not work
QTSession.open();
QTFile qtf = new QTFile (args[0]);
OpenMovieFile omf = OpenMovieFile.asRead(qtf);
Movie movie = Movie.fromFile (omf);
movie.start();
} catch (QTException qte) {
qte.printStackTrace();
}
With Java 1.4 on Mac OS X, this returns immediately, without playing any music. On Windows 2000 and XP, it seems to play a few seconds and hang. Either way, chances are you're not happy with the result. What has happened?
The problem has to do with tasking, the arcane art
of giving a QuickTime movie enough cycles to actually play itself.
Typically, when we build a QTJ application with a GUI, we pick up
the tasking calls automatically, and thus don't have to worry
about, or even know about, the need to periodically call
Movie.task(). In this case, we haven't picked up any
automatic tasking calls, nor set up any of our own.
On Windows, the side effect is that after getting time to play a
little bit of its buffer, the Movie is never given another chance to
decode and play the audio. The Mac OS X case is a little stranger -- I
believe what we're seeing is that our MP3 is handed off to a native
library, not actually played by Java, so the main() method
returns and the JVM, seeing no non-daemon threads running, decides to
shut down.
In any case, we need to provide regular callbacks to the
task() method to give our movie a chance to decode and
play the data. Fortunately, QTJ provides a class called
TaskAllMovies, which runs a thread that provides
tasking callbacks to all active movies. So we can solve our problems
on Mac and Windows by adding the two highlighted lines below after the
Movie object is created:
try {
//this version works
QTSession.open();
QTFile qtf = new QTFile (args[0]);
OpenMovieFile omf = OpenMovieFile.asRead(qtf);
Movie movie = Movie.fromFile (omf);
TaskAllMovies.addMovieAndStart();
movie.setActive (true);
movie.start();
} catch (QTException qte) {
qte.printStackTrace();
}
Call Me, Call Me
At this point, when the selected audio is finished, the application will just sit around forever. We'd like it to do something a little more sensible, like terminating the app at the end of the song. A kludgy approach would be to spawn a thread to periodically poll the movie and see if the current time has reached the end.
A better approach is to register to be notified when the movie is finished playing, using one of the callbacks that QuickTime provides. We can provide a small piece of code and tell QTJ to call this code at the end of the movie.
In the included sample CloseOnCallbackAudio.java, we
simply extend the simple player to register a callback that will be
called when the movie (the audio) finishes playing. This registration
is done with the callMeWhen() method:
callback = new ShutdownCallBack (movie);
callback.callMeWhen();
The ShutdownCallBack is an inner class that extends
QuickTime's ExtremesCallBack. In its constructor, we
indicate what Movie we're interested in (specifically, the
TimeBase of the movie), and provide flags to indicate on
which events we want to be called:
public ShutdownCallBack (Movie m)
throws QTException {
super (m.getTimeBase(),
StdQTConstants.triggerAtStop);
}
The callMeWhen() call does the actual registration
of the callback. This may seem a lot like registering a listener in
various Java APIs, but there's a big difference:
callMeWhen() only registers code for one
callback, as opposed to listeners that get called over and over until
they're specifically removed. To get that kind of behavior in QTJ,
we'd need to issue a new callMeWhen each time the
callback is executed.
When the callback is called, its execute() method is
called. Here's our simple implementation:
public void execute() {
System.out.println ("ShutdownCallBack.execute()");
cancelAndCleanup();
System.exit(0);
}
Note: The cancelAndCleanup() call is a
required call to disassociate our callback from QuickTime when we're
done using it. As the name suggests, there are two parts: a
"cancel" that cancels any pending callbacks from occurring,
and a "cleanup" that cleans up system resources. A separate
cancel() method exists to just cancel pending
callbacks. This would be useful if we wanted to reschedule or change
the conditions under which the code is called back — we would then
reschedule with a new call to callMeWhen().
As you might have expected from the fact that we subclassed
ExtremesCallBack, there are different classes to extend
in order to achieve different behaviors. All are subclasses of
QTCallBack, but provide different constructors, since
some take more detailed parameters. Each takes a
TimeBase, typically fetched from a Movie,
and some take a flags argument whose possible values are
defined as trigger... constants in the
StdQTConstants class.
| Class | Description |
ExtremesCallBack |
Called when the given TimeBase reaches its start or
stop point. You specify the behavior with the flags
triggerAtStart or triggerAtStop. |
RateCallBack |
Called when the TimeBase's rate changes. Using the
flag triggerRateChange provides a callback on any rate
change. Otherwise, you can use constants such as
triggerRateLT or triggerRateGT to get
called when the rate becomes less than, or greater than (respectively),
a supplied value. The full set of possible flags is listed in the documentation for the native CallMeWhen() function. |
TimeCallBack |
Called when a specific time value is reached. The flags
determine whether the callback occurs only when the time is moving
forward (triggerTimeFwd), backward
(triggerTimeBwd), or either
(triggerTimeEither). |
TimeJumpCallBack |
This callback occurs when the TimeBase's time value
changes by an amount other than would be expected from continuing to
play at its current rate. An obvious example would be when the user
clicks on the scrubber to "jump" to a different part of the
movie. Setting up this callback takes no behavior flags or
parameters. |
While this is primarily an article about audio, it should be clear that the callbacks have a wide range of uses in many QuickTime applications. For example, a movie-playing GUI may want to enable or disable some of its buttons and menu items, based on whether a movie is currently playing.
Pages: 1, 2 |