ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

A Gentle Re-Introduction to QuickTime for Java
Pages: 1, 2

Getting Started With QTJ

Let's assume that you really do need QuickTime for Java — you need to deliver as an application, you're writing a cool transcoding servlet, or whatever. Where should you begin?



First, check the system on your desk. Is it a Mac or a Windows box? Good. Sorry, no QuickTime on other operating systems, and thus no QTJ — and that includes CodeWeavers' CrossOver for Linux, which supports only the QuickTime browser plug-in, not QT in applications.

Next, you need to install QuickTime, which is available from Apple's site, if you don't already have it. I will assume from this point on that everyone is using QuickTime 6, which you need for some of the more interesting topics we'll be covering, such as MPEG-4. By the way, you don't need to purchase QuickTime Pro for QTJ development, though unlocking the editing and exporting features of Apple's player does make it easier to create test media for your development needs.

Mac OS X users can expect Java 1.3.1, QuickTime, and QuickTime for Java to be installed with the operating system. Windows users don't necessarily have any of these. Start at java.sun.com to get Java, then go get QuickTime from the link above. QuickTime for Java is not part of the "Recommended" QuickTime install, so you need to do a "Custom" install and specifically select QuickTime for Java.

QT installer (scaled)
Figure 1. Custom install of QuickTime on Windows

It's important to install Java first, since the QTJ installer needs to find Java installations on your system and install its QTJava.zip and QTJava.dll files where Java can find them.

There are QuickTime for Java SDKs available for download on Apple's site, but they are not strictly required for development, as all you really need to compile is the QTJava.zip file that is placed in your $JAVA_HOME/lib/ext directory. The SDK contains the javadocs and about 50 demo applications. While both are available on the web, it's well worth your time to get the SDK to have local copies.

Source Code

Download the source code for the simple player example.

Your First QTJ App

It's not quite HelloWorld, but a basic player is pretty much the first thing to create with a media API. Here's a simple player that asks the user to locate a QuickTime-playable file on the filesystem, opens the movie in a new frame, and plays it:

package com.mac.invalidname.simpleqtplayer;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.File;
import quicktime.*;
import quicktime.app.*;
import quicktime.app.players.*;
import quicktime.app.display.*;
import quicktime.io.*;
import quicktime.std.*;
import quicktime.std.movies.*;

public class SimpleQTPlayer extends Frame {

  Movie movie;

  public SimpleQTPlayer (String title) {
    super (title);
    try {
      QTSession.open();
      FileDialog fd = new FileDialog (this,
                                      "Select source movie",
                                      FileDialog.LOAD);
      fd.show();
      if (fd.getFile() == null)
          return;

      // get movie from file
      File f = new File (fd.getDirectory(), fd.getFile());
      OpenMovieFile omFile =
        OpenMovieFile.asRead (new QTFile (f));
      movie = Movie.fromFile (omFile);

      // get a Drawable for Movie, put in QTCanvas
      MoviePlayer player = new MoviePlayer (movie);
      QTCanvas canvas    = new QTCanvas();
      canvas.setClient (player, true);
      add (canvas);

      // windows-like close-to-quit
      addWindowListener (new WindowAdapter() {
        public void windowClosing (WindowEvent e) {
          QTSession.close();
          System.exit(0);
        }
      });
    } catch (Exception e) {
      e.printStackTrace();
    } 
  }

  public static void main (String[] args) {
    SimpleQTPlayer frame =
      new SimpleQTPlayer ("Simple QTJ Player");
    frame.pack();
    frame.setVisible(true);
    try {
      frame.movie.start();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Here's what the output looks like — in my case, a home movie running in a simple AWT Frame:

QuickTime movie playing in our simple player
Figure 2. Simple QTJ Player

Try it with different kinds of media that QuickTime supports, such as .mov and .avi videos, still images in various formats, and .mp3 and .wav audio files. (You'll get a zero-size window if you play an audio-only file.) This simple app should play most, if not all, of the supported media types.

Before we fight with the compiler — which will be a hassle the first time you do it — let's examine what the code does. The essential QuickTime stuff is in the constructor:

  • QTSession.open() initializes QuickTime and must be called before any other QTJ calls are made.
  • Movie.fromFile creates a Movie object from an OpenMovieFile.
  • A MoviePlayer is created from the Movie.
  • A QTCanvas object is created, and accepts a reference to the MoviePlayer in its setClient() method.
  • The QTCanvas is added as the only component of the Frame, making it the only thing we'll see.
  • An AWT WindowListener is set up to shut down QuickTime when the application goes away.
  • Finally, the main() method calls start() on the Movie, which starts playing in our window.

There are some peculiarities here: the number of imports is probably surprising for such a small application, and you might wonder if there aren't some unnecessary extra steps here. In particular, why do we hand the Movie to this MoviePlayer object? Why is it then sent to the QTPlayer, not in a constructor, but in the curiously named setClient method?

Don't worry, all will be covered in due course.

Let's look at compiling first. The QTJava.zip file that contains the QTJ classes is put in your Java extensions directory by the installer. Strangely, it only gets picked up for running applications and applets, not for compiling. When you compile, you must explicitly put the file in your CLASSPATH. On the command line, use a javac or jikes argument like -classpath /System/Library/Java/Extensions/QTJava.zip (that's the Mac OS X path; the Windows path will depend on where you installed the JDK). In an IDE, it's usually a matter of dragging and dropping QTJava.zip into your current project:


Figure 3. Dragging QTJava.zip into Project Builder project on Mac OS X

It will be pretty easy to tell if you don't have the CLASSPATH correct, as all of those imports will fail, looking something like this:

SimpleQTJPlayer.java:5: package quicktime does not exist
SimpleQTJPlayer.java:6: package quicktime.app does not exist
SimpleQTJPlayer.java:7: package quicktime.app.players does not exist
SimpleQTJPlayer.java:8: package quicktime.app.display does not exist

Obviously, this is enough of a hassle that if you are not using an IDE, you may want to set up a Makefile or an Ant build.xml file to save you from ongoing CLASSPATH misery. The example code for these articles will continue to provide Ant files to build the code, and you're welcome to use them for your own QTJ development.

There's one additional compile-time hassle: in making huge changes to how Java works in Mac OS X, Apple's 1.4.1 implementation is not currently compatible with QuickTime for Java. The problem comes from Apple's move to using Cocoa for their AWT/Swing implementation in 1.4.1, instead of Carbon, which they used for 1.3.1 and QTJ. I wrote about this in an O'Reilly weblog last month, and the problem has not been resolved as of this writing.

The workaround on Mac OS X is to use Java 1.3.1 explicitly, which will reportedly still be in the next major release of Mac OS X. In fact, since 1.4.1 is currently an optional download, it's safer to code to 1.3.1 anyway. Unfortunately, 1.4.1 becomes the default when installed, so it's a hassle. When working from the command line, you can specifically point to the 1.3.1 javac and java by using the path:

/System/Library/Frameworks/JavaVM.Framework/Versions/1.3.1/Commands

I've included this path in the sample code's my.ant.properties.mac file, which you need to rename to my.ant.properties to get picked up by ant. If you're using an IDE, there's probably a way to set your compiler and interpreter version to 1.3 in the GUI — in Project Builder, it's under "Java Compiler Settings" in the Target Settings:

Setting Project Builder to use java 1.3.1 for QTJ on Mac
Figure 4. Setting Project Builder to use Java 1.3.1 for QTJ on Mac

By the way, people have been asking me if I think the 1.4.1 problem means that QTJ is doomed and if they should go looking for another media API. For what it's worth, I prefer to just wait and see what happens — Apple hasn't officially dropped or deprecated QuickTime for Java. In the past, they've been pretty clear about when a technology is toast. Furthermore, Amazon still has a listing for a new edition of a QTJ book, written by Apple insiders, due in September. The situation is unclear — maybe we'll get more information at WWDC. In the meantime, while I'd prefer not having to work around the 1.4.1 situation, it's not time to panic. Not yet, anyway.

Movie and Other Essential Classes

In our example, it was simple to create a Movie object from a file reference, by way of the OpenMovieFile object. Appropriately enough, Movie is the heart and soul of the QuickTime for Java API, offering method calls for most common tasks:

  • Creating Movie objects from files, the system clipboard, memory locations, capture devices, and DataRef objects (commonly used as a wrapper for URLs), or creating empty Movies that we will create at runtime.
  • Getting metadata about the movie, including its duration, visual dimensions, preferred and current playback rate, and preferred and current volume.
  • Copying and pasting parts of a movie, or performing "low-level" inserts that don't involve the clipboard.
  • Starting and stopping playback.

It's important to think of a Movie not just in terms of what you can call on it, but also what it represents. In QuickTime, a Movie represents an organization of different kinds of time-based data, but it does not represent the data itself. In the QuickTime way of thinking, Movies have Tracks that refer to Media, which represent the low-level data like media samples (for example, audio samples or video frames).

This division allows you to go as deeply into the details as you need. Suppose you have a Movie that's made up of successive video tracks of different sizes. (You'll be able to create such a thing by the end of part 2.) If you want to know the size it will take up on screen, perhaps to create a rectangle big enough to accommodate all of the tracks, then you could call Movie.getBox(). But if you need to know the size of just one of those tracks, you could iterate over the Track objects, find the one you want, and call its getSize().

Another key concept of Movies is that they have a time scale, an integer that represents the time-keeping system within the movie. This value defaults to 600, meaning the movie has 600 time units per second. Values returned by methods like getDuration() and getTime() use this time scale, so a 30-second movie with a time scale of 600 returns 18000 for getDuration(). More interesting, though, is the fact that the various Media inside this Movie can and will have totally different time scales — uncompressed CD-quality audio might have a time scale of 44100, since it's 44.1 kHz — but the Movie isolates you from such details, so you don't have to worry about it, unless you want to iterate over the Media objects and investigate them specifically.

The Movie object wraps a structure and many functions from the native QuickTime API. Other classes in QTJ are unique to the Java version. The QTCanvas is an AWT component that offers a bridge from the QuickTime world to the Java display. To use a QTCanvas, call the setClient() method to hook it up to a class implementing the Drawable interface. Drawable is another QTJ-only creation, which indicates the ability of certain QTJ classes to provide pixels and sizing information to a QTCanvas. MoviePlayer is used for the simplest playback needs — other special-purpose Drawables exist for playing movies with a controller (QTPlayer), showing output from a capture device (SGDrawer), rendering effects (QTEffectPresenter), and for stream-broadcasting (PresentationDrawer).

Notice that I described the QTCanvas as an AWT component. SimpleQTPlayer is written entirely in AWT, not Swing. That's because the QTCanvas is a native component, allowing QuickTime to use hardware-accelerated rendering of your movie. The disadvantage, of course, is the difficulty of mixing AWT and Swing components. Because heavyweight AWT components will appear on top of any Swing components, a QTCanvas will appear above Swing JMenus, blasting through JTabbedPanes, and generally making a nuisance of itself, if you're not careful.

If you must use Swing for a QTJ app, there are two options. First, you can use the JQTCanvas, a lightweight Swing component introduced in QuickTime 6 that behaves like other JComponents in honoring Swing z-axis ordering. Unfortunately, its performance is generally poor; the movie must be re-imaged in software to get it into Swing's graphic space so that it can be painted. The second alternative is to carefully design your Swing layout to accomodate the heavyweight QTCanvas, not using overlapping components. The most common problem people experience with this is JMenus disappearing under the QTCanvas. You can get around this by calling setLightweightPopupEnabled (false) on your JMenus.

Coming soon ...

Now we've got a basic player, but of course, that's what we got from the QuickTime plug-in when we started. In Part 2, we'll delve into what makes QuickTime unique by getting into the editing API, allowing us to write a basic video editor with just a handful of code.

Chris Adamson is an author, editor, and developer specializing in iPhone and Mac.


Return to ONJava.com.