Chapter 2

Using the Media Tracker


CONTENTS


It's hard to talk about Java without the subject of multimedia popping up. Indeed, Java is the ideal technology to bring multimedia content to the Web. Knowing this, the Java architects have had to deal with a common problem associated with distributed media, transmission delay. Transmission delay refers to the amount of time it takes to transfer a media object across a network connection, and therefore how much time a Java applet must wait for images, sounds, and other media objects to be transferred.

The Java media tracker is a Java object that helps deal with the transmission delay problem by keeping up with when media objects have been successfully transmitted. Although the media tracker doesn't alleviate the delay in transmitting media objects, it does provide information regarding when objects have been transferred. In this chapter, you learn all about the Java media tracker, including how the media tracker is used to track media objects. You then get to see the benefits of the media tracker by implementing an applet both with and without media tracker support.

Java Media Objects and the Internet

One of the most important features of Java is its support for images and sound. No matter how many multimedia features Java provides, however, the problem of transmitting multimedia content over a limited bandwidth still exists. This means that the speed at which multimedia content is transferred over a Web connection often causes a noticeable delay in a Java applet reliant on media objects.

Of course, there isn't much that can be done in software to alleviate the physical transmission speed limitations of a network connection. However, there is a standard technique for dealing with transmission delay as it affects static images. You've no doubt seen this technique at work in your Web browser when viewing images in Web pages. The technique is known as interlacing and results in images appearing blurry until they have been completely transferred. To make use of interlacing, images must be stored in an interlaced format (usually GIF version 89a), which means that the image data is arranged in such a way that the image can be displayed before it is completely transmitted. Interlacing is a good approach to dealing with transmission delays for static images, because it enables you to see the image as it is being transferred. Without interlacing, you have to wait until the entire image has been transferred before seeing it at all.

Before you get too excited about interlacing, keep in mind that it's only useful for static images. This has to do with the fact that animations (dynamic images) rely on rapidly displaying a sequence of images over time, all of which must be available to create the effect of movement successfully. For more information about how animation works, look at Chapter 13, "Animation Techniques." An animation sequence just wouldn't look right using interlacing, because some of the images would be transferred before others. At this point, you may be thinking that a good solution would be to wait until all the images have been transferred before displaying the animation. Now you're thinking! But how do you know when the images have all been transferred? Enter the Java media tracker.

Think about how the transmission delay problem affects sound and music. Similar to animation, there isn't an interlacing workaround for sound and music. This is because sound is based on the passing of time, which means that there is no way to incrementally improve the sound quality without playing the sound. Once a sound is played, its time has passed. The same situation exists for music. Therefore, the media tracker presents a solution useful not only for animations, but also for sound and music. The drawback, as you'll learn later in this chapter, is that the media tracker currently supports only images.

Keeping Up with Media Objects

You've arrived at the logical conclusion that the best way to manage transmission delay effects on media objects is to simply keep up with when the objects have been successfully transferred. The Java media tracker is an object that performs this exact function. Using the media tracker, you can keep up with any number of media objects and query for when they have finished being transmitted.

For example, suppose you have an animation with four images. Using the media tracker, you would register each of these images and then wait until they have all been transferred before displaying the animation. The media tracker keeps up with the load status of each image. When the media tracker reports that all the images have been successfully loaded, you are guaranteed that your animation has all the necessary images to display correctly.

The MediaTracker Class

Not surprisingly, the Java media tracker is implemented as a class. The Java MediaTracker class is part of the AWT package and contains a variety of members and methods for tracking media objects. Unfortunately, the MediaTracker class that ships with release 1.0 of the Java Development Kit supports only the tracking of images. Future versions of Java are expected to add support for other types of media objects, such as sound, music, and animation.

Members

The MediaTracker class provides member flags for representing various states associated with tracked media objects. These flags are returned by many of the member functions of MediaTracker. Here are the MediaTracker member flags:

The LOADING flag indicates that a media object is currently in the process of being loaded. The ABORTED flag indicates that the loading of a media object has been aborted. The ERRORED flag indicates that some type of error occurred while trying to load a media object, and the COMPLETE flag indicates that a media object has been successfully loaded.

Methods

The MediaTracker class provides the following methods for helping to track media objects:

The constructor for MediaTracker takes a single parameter of type Component. This parameter specifies the Component object on which tracked images will eventually be drawn. This parameter reflects the current limitation of only being able to track images with the MediaTracker class.

The addImage methods add an image to the list of images currently being tracked. Both methods take as their first parameter an Image object and as their second parameter an identifier that uniquely identifies the image. If you want to track a group of images together, you can use the same identifier for each. The second addImage method has additional parameters for specifying the width and height of a tracked image. This version of addImage is used for tracking images that you are going to scale; you pass the width and height that you want to use for the scaled image.

Once images have been added to the MediaTracker object, you are ready to start checking their status. The checkID methods are used to check whether images matching the passed identifier have finished loading. Both versions of checkID return false if the images have not finished loading and true otherwise. This means that they return true even if the loading has been aborted or if an error has occurred. You must call the appropriate error checking methods to see whether an error occurred. You'll learn about the error checking methods a little later in this section.

The only difference between the two checkID methods is how the loading of images is handled. The first version of checkID does not load an image if it has not already begun loading. The second version, on the other hand, enables you to specify that the image be loaded if it hasn't already started loading. You specify this by passing true in the load parameter.

The checkAll methods are very similar to the checkID methods, except they apply to all images and not just those matching a certain identifier. Similar to the checkID methods, the checkAll methods come in two versions. The first version doesn't load any images that haven't already begun loading, and the second version enables you to indicate that images start loading if they haven't started already.

The waitForID methods are used to begin loading images with a certain identifier. Both versions of waitForID are synchronous, meaning that they do not return until all the specified images have finished loading or an error occurs. The second version of waitForID enables you to specify a time-out period, in which case the load will end, and waitForID will return true. You specify the time-out period in milliseconds by using the ms parameter.

The waitForAll methods are very similar to the waitForID methods, except they operate on all images. Like the waitForID methods, there are versions of waitForAll both with and without time-out support.

The statusID method is used to determine the status of images matching the identifier passed in the id parameter. statusID returns the bitwise OR of the status flags related to the images. The possible flags are LOADING, ABORTED, ERRORED, and COMPLETE. To check for a particular status flag, you simply mask the flag out of the return value of statusID:

if (tracker.statusID(0, true) & MediaTracker.ERRORED) {
  // there was an error!
}

The second parameter to statusID, load, should be familiar to you by now. It specifies whether you want the images to start loading if they haven't started already. This functionality is very similar to that provided by the second version of the checkID and waitForID methods.

The statusAll method is very similar to the statusId method, with the only difference being that statusAll returns the status of all the images being tracked, rather than those matching a specific identifier.

Finally, you arrive at the error checking methods mentioned earlier. The isErrorID and isErrorAny methods check the error status of images being tracked. The only difference is that the former checks on images with a certain identifier, and the latter checks on all images. Both of these methods basically check the status of each image for the ERRORED flag. Note that both methods will return true if any of the images have errors; it's up to you to determine which specific images have errors.

If you use isErrorID or isErrorAny and find out that there are load errors, you then need to find out which images had errors. You do this by using the getErrorsID and getErrorsAny methods. These two methods return an array of Objects containing the media objects that have load errors. In the current implementation of the MediaTracker, this array is always filled with Image objects. If there are no errors, these methods return null. Similar to the isErrorID and isErrorAny methods, getErrorsID and getErrorsAny differ only by the images that they check; the former returns errored images matching the passed identifier, and the latter returns all errored images.

That wraps up the description of the MediaTracker class. Now that you know the details about the class, you're ready to see it in action. Read on!

Using Images Without the Media Tracker

Tracking images with the MediaTracker class is pretty easy, and you'll learn how to do it soon enough. However, to better illustrate the impact of using the media tracker, you will first write an applet that doesn't use it. After seeing how the images are loaded and how the applet responds, you'll have more insight into why the media tracker is important. Figure 2.1 contains a screen shot of the Count1 applet.

Figure 2.1 : The Count1 applet with images fully loaded.

The Count1 applet displays a series of ten images horizontally one after the next. In an environment where the images are readily available, they would immediately be drawn when the applet is first run. However, with Java you are typically dealing with distributed applets, where the media objects used by an applet must be downloaded at runtime. This results in an appreciable delay between when the applet is run and when the images are available for display. (As you may have guessed, the screen shot of Count1 in Figure 2.1 was taken a few seconds after the applet had been run, meaning that I gave it time to finish loading the images. What I saw in the meantime is shown in Figure 2.2.)

Figure 2.2 : The Count1 applet with images partially loaded.

It's clear from Figure 2.2 that the images are in the middle of loading. Although the resulting effect is tolerable in this applet because the images are displayed statically, imagine what the effect would be if the applet were attempting to count to nine using the images. In this case, the images would be part of an animation and would yield very unpredictable results depending on the speed of the animation and the speed at which the applet was counting.

Before you learn how to resolve this problem, it's important to understand what is happening in Count1. Take a look at the source code for the Count1 applet in Listing 2.1.


Listing 2.1. The Count1 sample applet.
// Count1 Class
// Count1.java

// Imports
import java.applet.*;
import java.awt.*;

public class Count1 extends Applet {
  Image img[] = new Image[10];

  public void init() {
    for (int i = 0; i < 10; i++)
      img[i] = getImage(getDocumentBase(), "Res/" + i + ".gif");
  }

  public void update(Graphics g) {
    paint(g);
  }

  public void paint(Graphics g) {
    for (int i = 0; i < 10; i++)
      g.drawImage(img[i], i * 48, 0, this);
  }
}

Count1 has an array of ten Image objects as its only member variable, img. This array holds the ten number images that Count1 draws. The image array is initialized in the init method by loading each image in a for loop. The getImage method is used to load each image. Notice in the call to getImage that the images are expected to be stored in the Res subdirectory under the directory where the applet was launched.

In Count1, the update method is overridden so that there is no flicker when drawing the images. This is evident by the fact that this update method only calls paint. The original update method clears the background before calling paint. This is a pretty standard approach to eliminating flicker in simple Java applets. You learn about more powerful flicker reducing techniques in Chapter 13.

Finally, the images are drawn using the paint method. Drawing the images is as simple as iterating through the image array and calling the drawImage method for each image.

Tracking Images with the Media Tracker

Now that you have an idea how an applet works without the help of the media tracker, it's time to take a look at how the media tracker improves things. By using the media tracker, you know exactly when certain images have been transferred and are ready to use. This enables you to display alternate output based on whether the images have finished transferring. Figure 2.3 shows the Count2 applet while the images are still being loaded.

Figure 2.3 : The Count2 applet with images partially loaded.

As you can see, the images aren't displayed at all until they have all been successfully transferred. Instead, a text message is displayed informing the user that the images are still in the process of loading. This is a pretty simple enhancement to the applet, but one that makes the applet look much more professional. By displaying a simple message while media objects are loading, you solve the problem of drawing partially transferred images. Furthermore, you provide the user with more information about what is happening. If users have to wait for something, they are usually much more satisfied if you give them details about what they are waiting for.

Take a look at the source code for Count2 in Listing 2.2.


Listing 2.2. The Count2 sample applet.
// Count2 Class
// Count2.java

// Imports
import java.applet.*;
import java.awt.*;

public class Count2 extends Applet implements Runnable {
  Image         img[] = new Image[10];
  Thread        thread;
  MediaTracker  tracker;

  public void init() {
    tracker = new MediaTracker(this);
    for (int i = 0; i < 10; i++) {
      img[i] = getImage(getDocumentBase(), "Res/" + i + ".gif");
      tracker.addImage(img[i], 0);
    }
  }

  public void start() {
    thread = new Thread(this);
    thread.start();
  }

  public void stop() {
    thread.stop();
    thread = null;
  }

  public void run() {
    try {
      tracker.waitForID(0);
    }
    catch (InterruptedException e) {
      return;
    }
    repaint();
  }

  public void update(Graphics g) {
    paint(g);
  }

  public void paint(Graphics g) {
    if ((tracker.statusID(0, true) & MediaTracker.ERRORED) != 0) {
      g.setColor(Color.red);
      g.fillRect(0, 0, size().width, size().height);
      return;
    }
    if ((tracker.statusID(0, true) & MediaTracker.COMPLETE) != 0) {
      for (int i = 0; i < 10; i++)
        g.drawImage(img[i], i * 48, 0, this);
    }
    else {
      Font        font = new Font("Helvetica", Font.PLAIN, 18);
      FontMetrics fm = g.getFontMetrics(font);
      String      str = new String("Loading images...");
      g.setFont(font);
      g.drawString(str, (size().width - fm.stringWidth(str)) / 2,
        ((size().height - fm.getHeight()) / 2) + fm.getAscent());
    }
  }
}

It looks like a lot more code than Count1, but the extra code required to add media tracker support is really very simple. A lot of the extra code in Count2 is to draw the various outputs if the images aren't loaded.

The first thing you probably noticed in Count2 is the addition of two member variables, thread and tracker. The thread member is a Thread object that is used to provide the media tracker with its own stream of execution. This allows the media tracker to wait for the images to load without halting execution of the applet itself. The tracker member is the MediaTracker object used to track the images.

In the init method, the MediaTracker object is created by passing this as the only parameter to its constructor. If you recall from the discussion of the MediaTracker constructor earlier in this chapter, this parameter is of type Component, and it represents the component on which the tracked images will be drawn. All Applets are derived from Component, so passing the Applet object (this) correctly initializes the media tracker.

Also notice that the images are added to the media tracker in the Init method. This is accomplished by calling the addImage method of MediaTracker and passing the Image object and an identifier. Notice that 0 is passed as the identifier for all the images. This means that you are tracking them as a group.

The start and stop methods are used to manage the creation and destruction of the Thread member object. These are pretty standard implementations for adding basic multithreading support to an applet.

The run method is where the tracking actually starts taking place. The waitForID method of MediaTracker is called within a try-catch clause. It must be placed in this exception handling clause because an InterruptedException will be thrown if another thread interrupts this thread. Recall that waitForID is synchronous, meaning that it won't return until all the images with the specified identifier have been loaded. This means that the call to repaint will not occur until the images have all been loaded.

To understand why this works, you need to look at the last method in Count2, paint. The paint method begins by checking to see whether an error has occurred loading the images. It does this by calling statusID and checking the result against the ERRORED flag. If an error has occurred, paint fills the applet window with the color red to indicate an error. Figure 2.4 shows what Count2 looks like when an error occurs.

Figure 2.4 : The Count2 applet with an error in loading the images.

The next check performed by paint is to see whether the images have finished loading. It does this by calling statusID and comparing the result with the COMPLETE flag. If the images have finished loading, the image array is iterated through and each image drawn on the applet window. The resulting output is the same as Count1, which you already saw in Figure 2.1. If the images have not finished loading, the text message Loading images... is displayed. You may be curious about the font calculations that are made before drawing the text. These calculations are necessary to make sure the text is drawn centered in the applet window.

Tracking Other Types of Media

Now that you have a good idea how the media tracker is used to track images, you may be wondering what to do about other types of media, such as audio. Unfortunately, release 1.0 of Java doesn't provide media tracker support for any media types other than images. The reason for this is that few media types have been well-defined in this version of Java.

As of this writing, Sun has promised a future release of Java with more extensive support for other media types. Until then, you are pretty much left with tracking images only. It is technically possible to extend the media tracker yourself to support tracking audio clips, but it would require writing a fair amount of code that will ultimately be outdated when Sun provides its own implementations in the future. For more information on what the future audio support in Java might look like, check out Chapter 4, "Using Java's Audio Classes."

Summary

In this chapter, you learned about a problem inherent in transmitting media objects over the Internet, transmission delay. Because the Web is largely dependent on media objects, transmission delay is a very important issue that Java applets must be able to handle. Although there isn't really anything software can do to alleviate the physical transmission delays inherent in a particular network connection, there are suitable workarounds. One workaround that applies to static images is interlacing.

Although interlacing works well with static images, you learned in this chapter that the Java media tracker provides a universal solution to transmission delays as they apply to all media objects. Using the Java media tracker, you can be informed when media objects have finished transferring and are available for use. You saw a practical usage of the media tracker by writing a sample applet both with and without media tracker support. Finally, you learned that in its current release, the Java media tracker works only with images.