BUY THIS BOOK
Add to Cart

Print Book $29.95


Safari Books Online

What is this?

Add to UK Cart

Print Book £17.50

What is this?

Looking to Reprint this content?


QuickTime for Java: A Developer's Notebook
QuickTime for Java: A Developer's Notebook By Chris Adamson
January 2005
Pages: 255

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Getting Up and Running with QuickTime for Java
Do you need to do anything special to start developing QuickTime for Java applications? The answer to that question is easily answered by another question: are you using Mac OS X? If so, you have everything you need: Java, QuickTime, and QuickTime for Java (QTJ). If you're using Windows, you might have some downloading to do.
First, you must have Java installed, presumably the latest developer kit release from Sun. As of this writing, that would be the J2SE 1.4.2 SDK, which lives at http://java.sun.com/j2se/1.4.2/download.html. Now you must install and/or update QuickTime.
If you don't already have QuickTime (or iTunes, which includes QuickTime in its install), you can get it from http://quicktime.apple.com/. What's perhaps more common is that you have QuickTime, but you don't have QuickTime for Java, which is not installed by default.
In this case, you can use the QuickTime Updater to update your copy of QuickTime and install custom pieces like QTJ. If you have the QuickTime icon down in your System Tray, you can right-click it to launch the Updater, as seen in Figure 1-1. You can also get to the Updater via Start Programs QuickTime QuickTime Updater.
Figure 1-1: Launching the QuickTime Updater from the system tray
Whether you're updating or installing QuickTime for the first time, you need to click the Custom button to perform a custom install. This will give you the opportunity to install nondefault features, most of which are optional codecs , or software components to handle various video and audio encoding formats. Scroll down the list and you should see QuickTime for Java, as shown in Figure 1-2.
Figure 1-2: Custom install of QuickTime for Java
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Setting Up QTJ on Windows
First, you must have Java installed, presumably the latest developer kit release from Sun. As of this writing, that would be the J2SE 1.4.2 SDK, which lives at http://java.sun.com/j2se/1.4.2/download.html. Now you must install and/or update QuickTime.
If you don't already have QuickTime (or iTunes, which includes QuickTime in its install), you can get it from http://quicktime.apple.com/. What's perhaps more common is that you have QuickTime, but you don't have QuickTime for Java, which is not installed by default.
In this case, you can use the QuickTime Updater to update your copy of QuickTime and install custom pieces like QTJ. If you have the QuickTime icon down in your System Tray, you can right-click it to launch the Updater, as seen in Figure 1-1. You can also get to the Updater via Start Programs QuickTime QuickTime Updater.
Figure 1-1: Launching the QuickTime Updater from the system tray
Whether you're updating or installing QuickTime for the first time, you need to click the Custom button to perform a custom install. This will give you the opportunity to install nondefault features, most of which are optional codecs , or software components to handle various video and audio encoding formats. Scroll down the list and you should see QuickTime for Java, as shown in Figure 1-2.
Figure 1-2: Custom install of QuickTime for Java
Continue by clicking Update Now (or Install, if this is a new install) to put the latest version of QuickTime and QuickTime for Java on your PC.
The installer installed QuickTime's various pieces in your system, adding a QuickTime group to your Start Menu, a QuickTime icon in your System Tray, various pieces in
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Embedding QuickTime in HTML
Every once in a while, a developer new to QuickTime will post to one of the developer lists, saying he needs QTJ to put a QuickTime movie in a web page.
QTJ is great, but this is way, way overkill. For this task, you don't need QTJ. In fact, you'd just be creating headaches for yourself by requiring QTJ and dealing with the hassles of applets. Instead, you can just embed QuickTime content in HTML.
The mailing lists at http://lists.apple.com/ are a great source of information, particularly quicktime-java, quicktime-users (authoring), and quicktime-api (native programming). java-dev is also helpful for figuring out issues with Mac OS X's Java implementation.
In your HTML page, use an <object> tag, which wraps an <embed>, as shown in Example 1-1.
Example 1-1. Embedding QuickTime in HTML
<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
 width="160" height="136"
 codebase="http://www.apple.com/qtactivex/qtplugin.cab">
   <param name="src" VALUE="buhbuhbuh.mov"/>
   <param name="autoplay" VALUE="true"/>
   <param name="loop" VALUE="true"/>
   <param name="controller" value="true"/>
<embed src="buhbuhbuh.mov" width="160" height="136"
  scale="tofit"
  controller="true"
  autoplay="true"
  loop="true"
  pluginspage="http://www.apple.com/quicktime/download/"/>
</object>
The parameters are generally self-explanatory: height, width, and src are the only ones that are actually required. Because I've chosen to include a controller widget, I add 16 to the height parameter and use the scale parameter with the value tofit.
A web page using this tag is shown in Figure 1-4.
Figure 1-4: Embedding QuickTime movie in HTML
The weird thing about this is, of course, the tag-within-a-tag arrangement. We do this because although most browsers use the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Preflighting a QTJ Installation
Given the hassle of setting up your own box with a custom QuickTime installation, the idea of having to walk your Windows users through such a process is probably unappealing. Installing the various QuickTime .dlls and such by yourself is not an alternative, because you promised not to redistribute QuickTime when you clicked "agree" on that license.
You know the license I mean—it's the one you didn't read! That's OK, I didn't read it either.
Fortunately, QuickTime 6 offers a "preflighting" feature that allows you to create an XML file that describes what QuickTime features you need, open the file with QuickTime, and have QuickTime download and install your features if they're absent.
In your favorite text editor, create an XML file, as seen in Example 1-2.
Example 1-2. Preflighting to install QTJ
<?xml version="1.0"?>
<?quicktime type="application/x-qtpreflight"?>
<qtpreflight>
  <component type="null" subtype="qtj "/>
</qtpreflight>
Save this file with a .mov extension to associate it with QuickTime.
Have QuickTime open this file in whatever means is appropriate for your application—embed it in a web page, have an installer script open it with QuickTimePlayer.exe, etc. When you do, QuickTime will check to see if QuickTime for Java has been installed; if QTJ hasn't been installed, this will give the user a chance to download and install it, as seen in Figure 1-5.
Figure 1-5: Installing QuickTime for Java via preflighting
The XML file specifies a list of QuickTime components that the application knows it needs to run. These components are classified in a type/subtype scheme. For example, to test for MPEG-4 support, you'd use type "imdc" (short for image decompressor) and subtype "mp4v". QuickTime for Java is something of a special case, so it gets type "
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Compiling QTJ Code
Once you've installed QuickTime and QuickTime for Java, you have everything you need to start developing QTJ applications—no separate SDK is required.
You can begin by compiling a trivial application to check the QuickTime and QTJ versions, as shown in Example 1-3.
Example 1-3. Checking the version of QuickTime
package com.oreilly.qtjnotebook.ch01;
  
import quicktime.QTSession;
import quicktime.util.QTBuild;
  
public class QTVersionCheck {
  
  public static void main (String[  ] args) {
      try {
          QTSession.open( );
          System.out.println ("QT version: " +
              QTSession.getMajorVersion( ) +
              "." +
              QTSession.getMinorVersion( ));
          System.out.println ("QTJ version: " +
              QTBuild.getVersion( ) +
              "." +
              QTBuild.getSubVersion( ));
          QTSession.close( );
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
  
}
The compilation is the tricky step here. If you do a straightforward javac, bad things happen:
cadamson% javac src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java
src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java:3:
  package quicktime does not exist
import quicktime.QTSession;
               ^
src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java:4:
  package quicktime.util does not exist
import quicktime.util.QTBuild;
                    ^
src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java:10:
  cannot resolve symbol
symbol  : variable QTSession 
location: class com.oreilly.qtjnotebook.ch01.QTVersionCheck
          QTSession.open( );
          ^
Instead, you have to explicitly provide the path to QTJava.zip, which contains the QTJ classes. On the Mac OS X command line, you would do this as follows:
Here, as in many examples, you should type the entire command on one line. It's broken up in the text for printing purposes.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Opening and Closing the QuickTime Session
All QTJ applications are responsible for managing the QuickTime "session." The call to QTSession.open( ) gives QuickTime an opportunity to initialize itself, and it must be made before any other QTJ call, or you'll get an exception. Similarly, you must call QTSession.close( ) when you're done with QuickTime to give it a chance to clean up.
In general, this means you might want to call QTSession.open( ) as early as possible and QTSession.close( ) as late as possible. The former is easy enough to do: just put it in your application's entry point or even in a static initializer so that it precedes main( ). On the other hand, ensuring that you call QTSession.close( ) gracefully is trickier, because your user could quit your application with a menu item you provide, a Ctrl-C, a Cmd-Q (on Mac), or (heaven forbid) a kill -9 your-pid from the command line. Ideally, you'd like to have a fighting chance of properly closing QuickTime in as many cases as possible.
One way to close QuickTime late is to put QTSession.close() in a Java shutdown hook, which will get called as the JVM goes away. There are no guarantees, but it's better than nothing.
You can also run this example with the provided ant run-ch01-qtversioncheck task.
You can use the class in Example 1-4 as a general-purpose session handler for QTJ. It is presented here so that none of the other examples in the book will need to explicitly handle opening or closing the QTSession beyond calling this class.
Example 1-4. Session handler for QuickTime for Java
package com.oreilly.qtjnotebook.ch01;

import quicktime.*;

public class QTSessionCheck {

  private Thread shutdownHook;
  private static QTSessionCheck instance;
  private QTSessionCheck( ) throws QTException {
      super( );
      // init
      QTSession.open( );
      // create shutdown handler
      shutdownHook = new Thread( ) {
              public void run( ) {
                  QTSession.close( );
              }
          };
      Runtime.getRuntime( ).addShutdownHook(shutdownHook);
  }
  private static QTSessionCheck getInstance( ) throws QTException {
      if (instance =  = null)
          instance = new QTSessionCheck( );
      return instance;
  }
  
  public static void check( ) throws QTException {
      // gets instance.  if a new one needs to be created,
      // it calls QTSession.open( ) and creates a shutdown hook
      // to call QTSession.close( )
      getInstance( );
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Playing an Audio File from the Command Line
To finish this chapter, we'll look at a very simple example of QTJ code that actually plays some media. To keep things simple, I'll completely ignore the GUI, so all this will do is take a file path from the command line—presumably an MP3 or other audio file—and play it in QTJ.
Compile and run the source for TrivialAudioPlayer.java, shown in Example 1-5.
Example 1-5. Playing an audio file from the command line
package com.oreilly.qtjnotebook.ch01;
  
import quicktime.*;
import quicktime.app.time.*;
import quicktime.io.*;
import quicktime.std.*;
import quicktime.std.movies.*;
 
import java.io.*;
  
public class TrivialAudioPlayer {
 
  public static void main (String[  ] args) {
      if (args.length != 1) {
          System.out.println (
              "Usage: TrivialAudioPlayer <file>");
          return;
      }
      try {
          QTSessionCheck.check( );
          QTFile f = new QTFile (new File(args[0]));
          OpenMovieFile omf = OpenMovieFile.asRead(f);
          Movie movie = Movie.fromFile (omf);
          TaskAllMovies.addMovieAndStart( );
          movie.start( );
      } catch (QTException e) {
          e.printStackTrace( );
      }
  }
}
Once compiled, run it with the path to an audio file as a command-line argument. Note that if you downloaded the book examples and compiled with the ant buildfile, the classes will be in the classes directory, so you'll need to extend your classpath into there:
cadamson% java -classpath classes
  com.oreilly.qtjnotebook.ch01.TrivialAudioPlayer
  ~/mp3testing/Breakaway.mp3
This application provides a bare-bones load-and-play example. After checking that there's a valid argument, it does the QTSessionCheck from the previous task to set up the QuickTime session.
Any dynamic content in QuickTime is going to be a "movie," even if it's an audio-only file, like an MP3. This program also works for AACs, WAVs, iTunes Music Store songs, and anything else QuickTime can open
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Playing Movies
Even if you have more elaborate plans for QuickTime for Java, I'm going to assume that your plans will, at some point in time, require reading in a movie or other QuickTime-compatible file, locally or from the network. This chapter presents basic techniques of getting a Movie object, getting it into the Java display space, and adding more sophisticated controls so that your user (or just your code) can know what's happening inside a playing movie and take control.
I'll begin with "the simplest thing that could possibly work:" an application to ask the user for the location of a QuickTime file, which is then opened and put in a Java AWT Frame.
Example 2-1 shows the code for a simple movie player.
Example 2-1. Simple movie player
package com.oreilly.qtjnotebook.ch02;
 
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
import java.awt.*;
 
public class BasicQTPlayer extends Frame {
  public BasicQTPlayer (Movie m) throws QTException {
      super ("Basic QT Player");
      QTComponent qc = QTFactory.makeQTComponent (m);
      Component c = qc.asComponent( );
      add (c);
  }
 
  public static void main (String[  ] args) {
      try {
          QTSessionCheck.check( );
          QTFile file =
              QTFile.standardGetFilePreview (
          QTFile.kStandardQTFileTypes);
          OpenMovieFile omFile = OpenMovieFile.asRead (file);
          Movie m = Movie.fromFile (omFile);
          Frame f = new BasicQTPlayer (m);
          f.pack( );
          f.setVisible(true);
          m.start( );
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
}
Compile this from the command line (remember, as shown in the previous chapter, you must specify QTJava.zip in the classpath; this is the Mac OS X version):
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Building a Simple Movie Player
I'll begin with "the simplest thing that could possibly work:" an application to ask the user for the location of a QuickTime file, which is then opened and put in a Java AWT Frame.
Example 2-1 shows the code for a simple movie player.
Example 2-1. Simple movie player
package com.oreilly.qtjnotebook.ch02;
 
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
import java.awt.*;
 
public class BasicQTPlayer extends Frame {
  public BasicQTPlayer (Movie m) throws QTException {
      super ("Basic QT Player");
      QTComponent qc = QTFactory.makeQTComponent (m);
      Component c = qc.asComponent( );
      add (c);
  }
 
  public static void main (String[  ] args) {
      try {
          QTSessionCheck.check( );
          QTFile file =
              QTFile.standardGetFilePreview (
          QTFile.kStandardQTFileTypes);
          OpenMovieFile omFile = OpenMovieFile.asRead (file);
          Movie m = Movie.fromFile (omFile);
          Frame f = new BasicQTPlayer (m);
          f.pack( );
          f.setVisible(true);
          m.start( );
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
}
Compile this from the command line (remember, as shown in the previous chapter, you must specify QTJava.zip in the classpath; this is the Mac OS X version):
If you've downloaded the book code, compile and run with ant run-ch02-basicqtplayer.
cadamson% javac -d classes -classpath 
  src:/System/Library/Java/Extensions/QTJava.zip 
  src/com/oreilly/qtjnotebook/ch02/BasicQTPlayer.java
Then run the program from the command line:
cadamson% java -classpath classes
  com.oreilly.qtjnotebook.ch02.BasicQTPlayer
When the program starts up, the user will initially see QuickTime's file selector, shown in Figure 2-1.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Adding a Controller
This application isn't particularly user-friendly yet—the user can't start or stop the movie, move through it, or set the volume. Fortunately, it's easy to use a MovieController to get the standard QuickTime controller bar, an on-screen control widget that provides a play/pause button, a volume control, and the movie position control (typically called a "scrubber" in QuickTime parlance).
Create a new class in the source file BasicQTController.java. The main() is exactly the same as before, while the constructor adds one new line and changes another, as seen in Example 2-2.
Example 2-2. Getting a movie component with a controller
public class BasicQTController extends Frame {
 
  public BasicQTController (Movie m) throws QTException {
      super ("Basic QT Controller");
      MovieController mc = new MovieController(m);
      QTComponent qc = QTFactory.makeQTComponent (mc);
      Component c = qc.asComponent( );
      add (c);
      pack( );
  }
Compile and run this example with ant run-ch02-basicqtcontroller.
The result, when run, looks like the application in Figure 2-3. Notice the presence of the classic QuickTime control bar at the bottom of the window.
Figure 2-3: Movie with on-screen controller
This time, instead of asking the QTFactory to make a QTComponent from the Movie, the program creates a MovieController object from the Movie and asks the QTFactory to make a QTComponent from that. This eliminates the need for main( ) to call start( ), because the user can start and stop the movie from the play/pause button on the control bar.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Getting a Movie-Playing JComponent
The previous tasks have used the AWT, which seemingly nobody uses anymore. Many developers will want to create a Swing GUI, and thus they need a movie-playing JComponent. QuickTime for Java can provide one.
Example 2-3 presents a rewrite of the previous BasicQTPlayer that does everything with Swing equivalents (JFrame instead of Frame, JComponent instead of Component, etc.).
Example 2-3. All-Swing simple movie player
package com.oreilly.qtjnotebook.ch02;
 
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
import java.awt.*;
import javax.swing.*;
 
public class BasicSwingQTPlayer extends JFrame {
 
  public BasicSwingQTPlayer (Movie m) throws QTException {
      super ("Basic Swing QT Player");
      MoviePlayer mp = new MoviePlayer (m);
      QTJComponent qc = QTFactory.makeQTJComponent (mp);
      JComponent jc = qc.asJComponent( );
      getContentPane( ).add (jc);
      pack( );
  }
 
  public static void main (String[  ] args) {
      try {
          QTSessionCheck.check( );
          QTFile file =
              QTFile.standardGetFilePreview (
          QTFile.kStandardQTFileTypes);
          OpenMovieFile omFile = OpenMovieFile.asRead (file);
          Movie m = Movie.fromFile (omFile);
          JFrame f = new BasicSwingQTPlayer (m);
          f.pack( );
          f.setVisible(true);
          m.start( );
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
}
Compile and run this example with ant run-ch02-basicswingqtplayer.
This produces a simple movie-player window—as seen in Figure 2-4—using Swing, but visually indistinguishable from its AWT equivalent.
Figure 2-4: Playing a movie with a Swing JComponent
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Controlling a Movie Programmatically
For various reasons, an application might want to control the movie via its own method calls, in lieu of or in addition to the GUI provided by QuickTime's MovieController. One example of this is Movie.start( ). You can programmatically issue many more commands, some of which you can't issue with the default control.
Example 2-4 shows a new class, BasicQTButtons.java. The main() is exactly the same as BasicQTPlayer, but the constructor has extra work to create some control buttons, and an actionPerformed( ) method implements AWT's ActionListener.
Compile and run this example with ant run-ch02-basicqtbuttons..
Example 2-4. Programmatic control of a movie
package com.oreilly.qtjnotebook.ch02;
 
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
import java.awt.*;
import java.awt.event.*;
 
public class BasicQTButtons extends Frame
  implements ActionListener {
 
  Button revButton,
      stopButton,
      startButton,
      fwdButton;
 
  Movie theMovie;
 
  public BasicQTButtons (Movie m) throws QTException {
      super ("Basic QT Player");
      theMovie = m;
      QTComponent qc = QTFactory.makeQTComponent (m);
      Component c = qc.asComponent( );
      setLayout (new BorderLayout( ));
      add (c, BorderLayout.CENTER);
      Panel buttons = new Panel( );
      revButton = new Button("<");
      revButton.addActionListener (this);
      stopButton = new Button ("0");
      stopButton.addActionListener (this);
      startButton = new Button ("1");
      startButton.addActionListener (this);
      fwdButton = new Button (">");
      fwdButton.addActionListener (this);
      buttons.add (revButton);
      buttons.add (stopButton);
      buttons.add (startButton);
      buttons.add (fwdButton);
      add (buttons, BorderLayout.SOUTH);
      pack( );
  }
 
  public void actionPerformed (ActionEvent e) {
      try {
          if (e.getSource( ) =  = revButton)
              theMovie.setRate (theMovie.getRate( ) - 0.5f);
          else if (e.getSource( ) =  = stopButton)
              theMovie.stop( );
          else if (e.getSource( ) =  = startButton)
              theMovie.start( );
          else if (e.getSource( ) =  = fwdButton)
              theMovie.setRate (theMovie.getRate( ) + 0.5f);
      } catch (QTException qte) {
          qte.printStackTrace( );
      }
  }
 
  public static void main (String[  ] args) {
      try {
          QTSessionCheck.check( );
          QTFile file =
              QTFile.standardGetFilePreview (
                QTFile.kStandardQTFileTypes);
          OpenMovieFile omFile = OpenMovieFile.asRead (file);
          Movie m = Movie.fromFile (omFile);
          Frame f = new BasicQTButtons (m);
          f.pack( );
          f.setVisible(true);
          m.start( );
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
 
 
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Showing a Movie's Current Time
Advanced users, particularly those doing editing, would like to know a movie's current time—i.e., where they are in the movie. The scrubber can provide a general idea of the movie's current time, but certain applications call for an exact value.
Example 2-5s BasicQTTimeDisplay code extends the BasicQTController by adding a Label to the bottom of the Frame . A Swing Timer calls actionPerformed( ) every 250 milliseconds, and this method checks the current time of the movie and resets the label.
The Swing version of Timer is used to ensure that changing the label occurs on the AWT event-dispatch thread.
Compile and run this examnple with ant run-ch02-basicqttimedisplay.
Example 2-5. Displaying the current time of a movie
package com.oreilly.qtjnotebook.ch02;
 
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
import java.awt.*;
import java.awt.event.*;
 
public class BasicQTTimeDisplay extends Frame
  implements ActionListener {
  Movie theMovie;
  Label timeLabel;
 
  public BasicQTTimeDisplay (Movie m) throws QTException {
      super ("Basic QT Controller");
      theMovie = m;
      MovieController mc = new MovieController(m);
      QTComponent qc = QTFactory.makeQTComponent (mc);
      Component c = qc.asComponent( );
      setLayout (new BorderLayout( ));
      add (c, BorderLayout.CENTER);
      timeLabel = new Label ("-:--", Label.CENTER);
      add (timeLabel, BorderLayout.SOUTH);
      javax.swing.Timer timer =
          new javax.swing.Timer (250, this);
      timer.start( );
      pack( );
  }
 
  public void actionPerformed (ActionEvent e) {
      if (theMovie =  = null)
                  return;
      try {
          int totalSeconds = theMovie.getTime( ) /
                             theMovie.getTimeScale( );
          int seconds = totalSeconds % 60;
          int minutes = totalSeconds / 60;
          String secString = (seconds > 9) ?
              Integer.toString (seconds) :
              ("0" + Integer.toString (seconds));
          String minString = Integer.toString (minutes);
          timeLabel.setText (minString + ":" + secString);
      } catch (QTException qte) {
          qte.printStackTrace( );
      }
  }
 
  public static void main (String[  ] args) {
      try {
          QTSessionCheck.check( );
          QTFile file =
              QTFile.standardGetFilePreview (
                 QTFile.kStandardQTFileTypes);
          OpenMovieFile omFile = OpenMovieFile.asRead (file);
          Movie m = Movie.fromFile (omFile);
          Frame f = new BasicQTTimeDisplay (m);
          f.pack( );
          f.setVisible(true);
          m.start( );
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Listening for Movie State-Changes
One problem with polling to show the current time in the movie is that it's wasteful and inaccurate: it's optimal to check the time only when the movie's playing, and to eliminate latency, it would be nice to be notified when there's a sudden change in the current time, such as when the user slides the scrubber. Fortunately, there's a callback API to notify a program when things like this occur.
This example revises the BasicQTButtons program. The new version, BasicQTCallback, asks to be notified when the rate changes. When the rate is 0, it will disable the stop button (labeled "0"), and when the rate is 1, it disables the play button (labeled "1"). For space, I'll list only the lines that have changed from BasicQTButtons.
First, there are two new imports: quicktime.std.clocks, which is where callbacks are defined, and quicktime.std, whose StdQTConstants provides constants to specify the callbacks' behavior:
import quicktime.std.*;
import quicktime.std.clocks.*;
Next, the constructor is changed to pass the Movie to an inner class' constructor:
MyQTCallback myCallback = new MyQTCallback (m);
And here's the inner class. It has a constructor that takes a Movie argument and an execute( ) method:
class MyQTCallback extends RateCallBack {
  public MyQTCallback (Movie m) throws QTException {
      super (m.getTimeBase( ),
             0,
             StdQTConstants.triggerRateChange);
      callMeWhen( );
  }
  public void execute( ) {
      if (rateWhenCalled =  = 0.0) {
          startButton.setEnabled (true);
          stopButton.setEnabled (false);
      } else if (rateWhenCalled =  = 1.0) {
          startButton.setEnabled (false);
          stopButton.setEnabled (true);
      }
      // indicate that we want to be called again
      try {
          callMeWhen( );
      } catch (QTException qte) {
          qte.printStackTrace( );
      }
  }
}
The result looks like the window in Figure 2-7. Notice how in the screenshot, the stop button ("0") is dimmed, indicating that the movie is already stopped. If the user hits "1," the movie will play and the play button will be disabled.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Moving Frame by Frame
One popular feature for playback is the ability to step forward exactly one frame. It turns out to be trickier than one might initially expect: it's not like there's a Movie.nextFrame( ) method. Indeed, a Movie might not have a video track at all, if it represents an MP3 or some other audio-only media. So, finding the next frame requires being a little smarter about looking inside the Movie's structure.
This example builds on the earlier BasicQTButtons code. In this example, the implementations of the forward and back buttons are altered so that instead of changing the play rate, they change the current time to be the next frame before or after the current time. For space, this example shows only the changes from the original BasicQTButtons.
This example needs to import quicktime.std to use StdQTConstants , and quicktime.std.clocks for some time-related classes. It also adds an instance variable visualTrack, which is found with the following call:
theMovie = m;
// find video track
visualTrack =
  m.getIndTrackType (1,
                     StdQTConstants.visualMediaCharacteristic,
                     StdQTConstants.movieTrackCharacteristic);
If a visual track isn't found, the revButton and fwdButton are disabled later in the constructor.
Finally, a new implementation of actionPerformed() does the frame-step logic when the revButton or fwdButton is clicked:
if (e.getSource( ) =  = revButton) {
  TimeInfo ti =
      visualTrack.getNextInterestingTime (
            StdQTConstants.nextTimeMediaSample,
            theMovie.getTime( ),
            -1);
  theMovie.setTime (new TimeRecord (theMovie.getTimeScale( ),
                                    ti.time));
}
else if (e.getSource( ) =  = stopButton)
  theMovie.stop( );
else if (e.getSource( ) =  = startButton)
  theMovie.start( );
else if (e.getSource( ) =  = fwdButton) {
  TimeInfo ti =
      visualTrack.getNextInterestingTime (
            StdQTConstants.nextTimeMediaSample,
            theMovie.getTime( ),
            1);
  theMovie.setTime (new TimeRecord (theMovie.getTimeScale( ),
                                    ti.time));
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Playing Movies from URLs
Along with loading movies from disk, QuickTime can also load them from URLs, and is pretty smart about network latency.
Example 2-6 shows a totally new class, BasicQTURLController.java. This is a significant rethinking of the earlier BasicQTController class. This example creates a GUI from an empty "dummy" movie, then asks the user for a URL, gets a Movie from that, and replaces the dummy movie. By getting the movie last, it tempts fate to see how well QTJ can deal with loading a movie over the network.
Example 2-6. Loading and playing a movie from a URL
package com.oreilly.qtjnotebook.ch02;
 
import quicktime.*;
import quicktime.std.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.std.movies.media.*;
import quicktime.io.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
import java.awt.*;
 
public class BasicQTURLController extends Frame {
 
  QTComponent qc;
 
  public BasicQTURLController ( ) throws QTException {
      super ("Basic QT DataRef/Controller");
      Movie dummyMovie = new Movie( );
      qc = QTFactory.makeQTComponent (dummyMovie);
      Component c = qc.asComponent( );
      add (c);
      pack( );
  }
 
  public static void main (String[  ] args) {
      try {
          QTSessionCheck.check( );
          BasicQTURLController f =
              new BasicQTURLController ( );
          String url =
              javax.swing.JOptionPane.showInputDialog (f,
                                              "Enter URL");
          DataRef dr = new DataRef (url);
          Movie m = Movie.fromDataRef (dr,
                                 StdQTConstants.newMovieActive);
          MovieController mc = new MovieController (m);
          f.qc.setMovieController (mc);
          f.setVisible(true);
          f.pack( );
          m.prePreroll(0, 1.0f);
          m.preroll(0, 1.0f);
          m.start( );
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Preventing "Tasking" Problems
All the tasks in this chapter have managed to avoid one of the more obscure hazards in QuickTime. This example tempts fate and exposes the problem by playing a movie that would otherwise freeze up.
Example 2-7 reprises the command-line audio player from the first chapter, which takes a path to a file as a command-line argument, builds a Movie, and plays it, without getting any kind of GUI.
Example 2-7. Playing audio from the command line
package com.oreilly.qtjnotebook.ch01;
 
import quicktime.*;
import quicktime.app.time.*;
import quicktime.io.*;
import quicktime.std.*;
import quicktime.std.movies.*;
 
import java.io.*;
 
public class BasicAudioPlayer {
 
  public static void main (String[  ] args) {
      if (args.length != 1) {
          System.out.println (
              "Usage: BasicAudioPlayer <file>");
          return;
      }
      try {
          QTSessionCheck.check( );
          QTFile f = new QTFile (new File(args[0]));
          OpenMovieFile omf = OpenMovieFile.asRead(f);
          Movie movie = Movie.fromFile (omf);
          TaskAllMovies.addMovieAndStart( );
          movie.start( );
      } catch (QTException e) {
          e.printStackTrace( );
      }
  }
}
Notice the line in bold. Take it out, recompile, and watch what happens. The program will likely hang or immediately exit, playing just a spurt of audio or none at all.
QuickTime movies need to explicitly be given CPU time to do their work: reading from disk or the network, decompressing and decoding, rendering to the screen, or playing to the speakers. This process is called "tasking." Looking at the Javadocs reveals that the Movie class has a task( ) method that could be called to give time to a specific movie, and a static taskAll( ) method that tasks all active movies.
Managing all these calls manually and being sure to call them frequently enough would be, of course, incredibly tedious. That's why QTJ provides
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Editing Movies
Playback is nice, but you have nothing to play if you lack tools to create media, and the most critical of these are editing tools. If you've ever used iMovie with your home movies, you know what I'm talking about: there's a huge difference between watching a cute collection of scenes of your kids playing, set to music, and watching the two hours of unedited raw footage you started with. Sometimes, less is more.
The most familiar form of editing is copy-and-paste, which many users already are familiar with from the "pro" version of QuickTime Player. The metaphor is identical to how copy-and-paste works in nonmedia applications such as text editors and spreadsheets: select some source material of interest, do a "copy" to put it on the system clipboard, select an insertion point in this or another document, and do a "paste" to put the contents of the clipboard into that target.
In the simplest form of a QuickTime copy-and-paste, the controller bar (from MovieController) is used to indicate where copies and pastes should occur. By shift-clicking, a user can select a time-range from the current time (indicated by the play head) to wherever the user shift-clicks (or, if he is dragging, wherever the mouse is released).
QuickTime Pro costs money ($29.99 as of this writing), but it allows you to exercise much of the QuickTime API from QuickTime Player, which can be a useful debugging tool.
BasicQTEditor, shown in Example 3-1, will be the basis for the examples in this chapter. It offers a single empty movie window (with the ability to open movies from disk in new windows or to create new empty movie windows), and an Edit menu with cut, copy, and paste options.
Example 3-1. A copy-and-paste movie editor
package com.oreilly.qtjnotebook.ch03;
 
import quicktime.*;
import quicktime.qd.QDRect;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.app.view.*;
import quicktime.io.*;
 
import java.awt.*;
import java.awt.event.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
public class BasicQTEditor extends Frame
  implements ActionListener {
 
  Component comp;
 
  Movie movie;
  MovieController controller;
  Menu fileMenu, editMenu;
  MenuItem openItem, closeItem, newItem, quitItem;
  MenuItem copyItem, cutItem, pasteItem;
  static int newFrameX = -1;
  static int newFrameY = -1;
  static int windowCount = 0;
  
  /** no-arg constructor for "new" movie
   */
  public BasicQTEditor ( ) throws QTException {
      super ("BasicQTEditor");
      setLayout (new BorderLayout( ));
      QTSessionCheck.check( );
      movie = new Movie(StdQTConstants.newMovieActive);
      controller = new MovieController (movie);
      controller.enableEditing(true);
      doMyLayout( );
  }
 
  /** file-based constructor for opening movies
   */
  public BasicQTEditor (QTFile file) throws QTException {
      super ("BasicQTEditor");
      setLayout (new BorderLayout( ));
      QTSessionCheck.check( );
      OpenMovieFile omf = OpenMovieFile.asRead (file);
      movie = Movie.fromFile (omf);
      controller = new MovieController (movie);
      controller.enableEditing(true);
      doMyLayout( );
  }
  /** gets component from controller, makes menus
   */
  private void doMyLayout( ) throws QTException {
      // add movie component
      QTComponent qtc =
          QTFactory.makeQTComponent (controller);
      comp = qtc.asComponent( );
      add (comp, BorderLayout.CENTER);
      // file menu
      fileMenu = new Menu ("File");
      newItem = new MenuItem ("New Movie");
      newItem.addActionListener (this);
      fileMenu.add (newItem);
      openItem = new MenuItem ("Open Movie...");
      openItem.addActionListener (this);
      fileMenu.add (openItem);
      closeItem = new MenuItem ("Close");
      closeItem.addActionListener (this);
      fileMenu.add (closeItem);
      fileMenu.addSeparator( );
      quitItem = new MenuItem ("Quit");
      quitItem.addActionListener (this);
      fileMenu.add(quitItem);
      // edit menu
      editMenu = new Menu ("Edit");
      copyItem = new MenuItem ("Copy");
      copyItem.addActionListener(this);
      editMenu.add(copyItem);
      cutItem = new MenuItem ("Cut");
      cutItem.addActionListener(this);
      editMenu.add(cutItem);
      pasteItem = new MenuItem ("Paste");
      pasteItem.addActionListener(this);
      editMenu.add(pasteItem);
      // make menu bar
      MenuBar bar = new MenuBar( );
      bar.add (fileMenu);
      bar.add (editMenu);
      setMenuBar (bar);
      // add close-button handling
      addWindowListener (new WindowAdapter( ) {
              public void windowClosing (WindowEvent e) {
                  doClose( );
              }
          });
  }
 
  /** handles menu actions
   */
  public void actionPerformed (ActionEvent e) {
      Object source = e.getSource( );
      try {
          if (source =  = quitItem) doQuit( );
          else if (source =  = openItem) doOpen( );
          else if (source =  = closeItem) doClose( );
          else if (source =  = newItem) doNew( );
          else if (source =  = copyItem) doCopy( );
          else if (source =  = cutItem) doCut( );
          else if (source =  = pasteItem) doPaste( );
      } catch (QTException qte) {
          qte.printStackTrace( );
      }
  }
 
  public void doQuit( ) {
      System.exit(0);
  }
 
  public void doNew( ) throws QTException {
      makeNewAndShow( );
  }
 
  public void doOpen( ) throws QTException {
      QTFile file =
          QTFile.standardGetFilePreview (QTFile.kStandardQTFileTypes);
      Frame f = new BasicQTEditor (file);
      f.pack( );
      if (newFrameX >= 0)
          f.setLocation (newFrameX+=16, newFrameY+=16);
      f.setVisible(true);
      windowCount++;
  }
 
  public void doClose( ) {
      setVisible(false);
      dispose( );
      // quit if no windows now showing
      if (--windowCount =  = 0)
          doQuit( );
  }
 
  public void doCopy( ) throws QTException {
      Movie copied = controller.copy( );
      copied.putOnScrap(0);
  }
 
  public void doCut( ) throws QTException {
      Movie cut = controller.cut( );
      cut.putOnScrap(0);
  }
 
  public void doPaste( ) throws QTException {
      controller.paste( );
      pack( );
  }
 
/** Force frame's size to respect movie size
   */
  public Dimension getPreferredSize( ) {
      System.out.println ("getPreferredSize");
      if (controller =  = null)
          return new Dimension (0,0);
      try {
          QDRect contRect = controller.getBounds( );
          Dimension compDim = comp.getPreferredSize( );
          if (contRect.getHeight( ) > compDim.height) {
              return new Dimension (contRect.getWidth( ) +
                                    getInsets( ).left +
                                    getInsets( ).right,
                                    contRect.getHeight( ) +
                                    getInsets( ).top +
                                    getInsets( ).bottom);
 
          } else {
              return new Dimension (compDim.width +
                                    getInsets( ).left +
                                    getInsets( ).right,
                                    compDim.height +
                                    getInsets( ).top +
                                    getInsets( ).bottom);
 
          }
      } catch (QTException qte) {
          return new Dimension (0,0);
      }
  }
 
  /** opens a single new movie window
   */
  public static void main (String[  ] args) {
      try {
          Frame f = makeNewAndShow( );
          // note its x, y for future calls
          newFrameX = f.getLocation( ).x;
          newFrameY = f.getLocation( ).y;
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
 
  /** creates "new" movie frame, packs and shows.
      used by main( ) and "new"
   */
  private static Frame makeNewAndShow( )
      throws QTException {
      Frame f = new BasicQTEditor( );
      f.pack( );
      if (newFrameX >= 0)
          f.setLocation (newFrameX+=16, newFrameY+=16);
      f.setVisible(true);
      windowCount++;
      return f;
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Copying and Pasting
The most familiar form of editing is copy-and-paste, which many users already are familiar with from the "pro" version of QuickTime Player. The metaphor is identical to how copy-and-paste works in nonmedia applications such as text editors and spreadsheets: select some source material of interest, do a "copy" to put it on the system clipboard, select an insertion point in this or another document, and do a "paste" to put the contents of the clipboard into that target.
In the simplest form of a QuickTime copy-and-paste, the controller bar (from MovieController) is used to indicate where copies and pastes should occur. By shift-clicking, a user can select a time-range from the current time (indicated by the play head) to wherever the user shift-clicks (or, if he is dragging, wherever the mouse is released).
QuickTime Pro costs money ($29.99 as of this writing), but it allows you to exercise much of the QuickTime API from QuickTime Player, which can be a useful debugging tool.
BasicQTEditor, shown in Example 3-1, will be the basis for the examples in this chapter. It offers a single empty movie window (with the ability to open movies from disk in new windows or to create new empty movie windows), and an Edit menu with cut, copy, and paste options.
Example 3-1. A copy-and-paste movie editor
package com.oreilly.qtjnotebook.ch03;
 
import quicktime.*;
import quicktime.qd.QDRect;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.app.view.*;
import quicktime.io.*;
 
import java.awt.*;
import java.awt.event.*;
 
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
 
public class BasicQTEditor extends Frame
  implements ActionListener {
 
  Component comp;
 
  Movie movie;
  MovieController controller;
  Menu fileMenu, editMenu;
  MenuItem openItem, closeItem, newItem, quitItem;
  MenuItem copyItem, cutItem, pasteItem;
  static int newFrameX = -1;
  static int newFrameY = -1;
  static int windowCount = 0;
  
  /** no-arg constructor for "new" movie
   */
  public BasicQTEditor ( ) throws QTException {
      super ("BasicQTEditor");
      setLayout (new BorderLayout( ));
      QTSessionCheck.check( );
      movie = new Movie(StdQTConstants.newMovieActive);
      controller = new MovieController (movie);
      controller.enableEditing(true);
      doMyLayout( );
  }
 
  /** file-based constructor for opening movies
   */
  public BasicQTEditor (QTFile file) throws QTException {
      super ("BasicQTEditor");
      setLayout (new BorderLayout( ));
      QTSessionCheck.check( );
      OpenMovieFile omf = OpenMovieFile.asRead (file);
      movie = Movie.fromFile (omf);
      controller = new MovieController (movie);
      controller.enableEditing(true);
      doMyLayout( );
  }
  /** gets component from controller, makes menus
   */
  private void doMyLayout( ) throws QTException {
      // add movie component
      QTComponent qtc =
          QTFactory.makeQTComponent (controller);
      comp = qtc.asComponent( );
      add (comp, BorderLayout.CENTER);
      // file menu
      fileMenu = new Menu ("File");
      newItem = new MenuItem ("New Movie");
      newItem.addActionListener (this);
      fileMenu.add (newItem);
      openItem = new MenuItem ("Open Movie...");
      openItem.addActionListener (this);
      fileMenu.add (openItem);
      closeItem = new MenuItem ("Close");
      closeItem.addActionListener (this);
      fileMenu.add (closeItem);
      fileMenu.addSeparator( );
      quitItem = new MenuItem ("Quit");
      quitItem.addActionListener (this);
      fileMenu.add(quitItem);
      // edit menu
      editMenu = new Menu ("Edit");
      copyItem = new MenuItem ("Copy");
      copyItem.addActionListener(this);
      editMenu.add(copyItem);
      cutItem = new MenuItem ("Cut");
      cutItem.addActionListener(this);
      editMenu.add(cutItem);
      pasteItem = new MenuItem ("Paste");
      pasteItem.addActionListener(this);
      editMenu.add(pasteItem);
      // make menu bar
      MenuBar bar = new MenuBar( );
      bar.add (fileMenu);
      bar.add (editMenu);
      setMenuBar (bar);
      // add close-button handling
      addWindowListener (new WindowAdapter( ) {
              public void windowClosing (WindowEvent e) {
                  doClose( );
              }
          });
  }
 
  /** handles menu actions
   */
  public void actionPerformed (ActionEvent e) {
      Object source = e.getSource( );
      try {
          if (source =  = quitItem) doQuit( );
          else if (source =  = openItem) doOpen( );
          else if (source =  = closeItem) doClose( );
          else if (source =  = newItem) doNew( );
          else if (source =  = copyItem) doCopy( );
          else if (source =  = cutItem) doCut( );
          else if (source =  = pasteItem) doPaste( );
      } catch (QTException qte) {
          qte.printStackTrace( );
      }
  }
 
  public void doQuit( ) {
      System.exit(0);
  }
 
  public void doNew( ) throws QTException {
      makeNewAndShow( );
  }
 
  public void doOpen( ) throws QTException {
      QTFile file =
          QTFile.standardGetFilePreview (QTFile.kStandardQTFileTypes);
      Frame f = new BasicQTEditor (file);
      f.pack( );
      if (newFrameX >= 0)
          f.setLocation (newFrameX+=16, newFrameY+=16);
      f.setVisible(true);
      windowCount++;
  }
 
  public void doClose( ) {
      setVisible(false);
      dispose( );
      // quit if no windows now showing
      if (--windowCount =  = 0)
          doQuit( );
  }
 
  public void doCopy( ) throws QTException {
      Movie copied = controller.copy( );
      copied.putOnScrap(0);
  }
 
  public void doCut( ) throws QTException {
      Movie cut = controller.cut( );
      cut.putOnScrap(0);
  }
 
  public void doPaste( ) throws QTException {
      controller.paste( );
      pack( );
  }
 
/** Force frame's size to respect movie size
   */
  public Dimension getPreferredSize( ) {
      System.out.println ("getPreferredSize");
      if (controller =  = null)
          return new Dimension (0,0);
      try {
          QDRect contRect = controller.getBounds( );
          Dimension compDim = comp.getPreferredSize( );
          if (contRect.getHeight( ) > compDim.height) {
              return new Dimension (contRect.getWidth( ) +
                                    getInsets( ).left +
                                    getInsets( ).right,
                                    contRect.getHeight( ) +
                                    getInsets( ).top +
                                    getInsets( ).bottom);
 
          } else {
              return new Dimension (compDim.width +
                                    getInsets( ).left +
                                    getInsets( ).right,
                                    compDim.height +
                                    getInsets( ).top +
                                    getInsets( ).bottom);
 
          }
      } catch (QTException qte) {
          return new Dimension (0,0);
      }
  }
 
  /** opens a single new movie window
   */
  public static void main (String[  ] args) {
      try {
          Frame f = makeNewAndShow( );
          // note its x, y for future calls
          newFrameX = f.getLocation( ).x;
          newFrameY = f.getLocation( ).y;
      } catch (Exception e) {
          e.printStackTrace( );
      }
  }
 
  /** creates "new" movie frame, packs and shows.
      used by main( ) and "new"
   */
  private static Frame makeNewAndShow( )
      throws QTException {
      Frame f = new BasicQTEditor( );
      f.pack( );
      if (newFrameX >= 0)
          f.setLocation (newFrameX+=16, newFrameY+=16);
      f.setVisible(true);
      windowCount++;
      return f;
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Performing "Low-Level" Edits
Low-level edits are a separate set of editing calls that don't involve the clipboard or selection metaphors. They're called "low level" because instead of operating at the conceptual level of "paste the contents of the clipboard into the user's current selection," they work at the level of "insert a segment from movie M1, ranging from time A to time B, into movie M2 at time C."
By way of comparison, although QuickTime has two sets of editing functions, Sun's Java Media Framework has no editing API at all.
This version reimplements doCopy( ), doCut( ), and doPaste( ) to use low-level editing calls on the Movie instead of cut/copy/paste-type calls on the MovieController.
First, LowLevelQTEditor needs a static Movie, called copiedMovie, to keep track of what's on its virtual "clipboard" so that it can be shared across the new doCopy( ), doCut(), and doPaste( ) methods:
public void doCopy( ) throws QTException {
      copiedMovie = new Movie( );
      TimeInfo selection = movie.getSelection( );
      movie.insertSegment (copiedMovie,
                           selection.time,
                           selection.duration,
                           0);
  }
 
public void doCut( ) throws QTException {
      copiedMovie = new Movie( );
      TimeInfo selection = movie.getSelection( );
      movie.insertSegment (copiedMovie,
                           selection.time,
                           selection.duration,
                           0);
      movie.deleteSegment (selection.time,
                           selection.duration);
      controller.movieChanged( );
  }
 
public void doPaste( ) throws QTException {
      if (copiedMovie =  = null)
          return;
      copiedMovie.insertSegment (movie,
                                 0,
                                 copiedMovie.getDuration( ),
                                 movie.getSelection( ).time);
      controller.movieChanged( );
      pack( );
  }
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Undoing an Edit
Critical to any kind of editing is the ability to back out of a change that had unintended or undesirable effects. Fortunately, controller-based cuts and pastes can be undone with some fairly simple calls.
UndoableQTEditor builds on the original BasicQTEditor by adding an "undo" menu item. The doUndo( ) method i