acm.util.MovieClip Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javakarel Show documentation
Show all versions of javakarel Show documentation
This the original Stanford Karel for Java, packaged for Maven. ACM Library is included. See also https://cs.stanford.edu/people/eroberts/karel-the-robot-learns-java.pdf
The newest version!
/*
* @(#)MovieClip.java 1.99.1 08/12/08
*/
// ************************************************************************
// * Copyright (c) 2008 by the Association for Computing Machinery *
// * *
// * The Java Task Force seeks to impose few restrictions on the use of *
// * these packages so that users have as much freedom as possible to *
// * use this software in constructive ways and can make the benefits of *
// * that work available to others. In view of the legal complexities *
// * of software development, however, it is essential for the ACM to *
// * maintain its copyright to guard against attempts by others to *
// * claim ownership rights. The full text of the JTF Software License *
// * is available at the following URL: *
// * *
// * http://www.acm.org/jtf/jtf-software-license.pdf *
// * *
// ************************************************************************
// REVISION HISTORY
//
// Class introduced in V1.1
package acm.util;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
/* Implementation notes */
/*
* This class uses QuickTime for Java, which is not available on all Java platforms.
* To ensure that this class can be compiled even on systems that lack the QuickTime
* libraries, all calls to the library are implemented through reflection.
*/
/* Class: MovieClip */
/**
* This class represents a video clip, which can be read from a QuickTime
* movie file or web-based resource.
*/
public class MovieClip extends Container {
/* Constructor: MovieClip(filename) */
/**
* Creates a MovieClip
object from the specified movie file.
*
* Example: MovieClip movie = new MovieClip(filename);
* @param filename The file from which the movie is read
*/
public MovieClip(String filename) {
this(filename, filename);
}
/* Constructor: MovieClip(file) */
/**
* Creates a MovieClip
object from the specified movie file.
*
* Example: MovieClip movie = new MovieClip(file);
* @param file A File
object from which the movie is read
*/
public MovieClip(File file) {
this(file, file.getName());
}
/* Constructor: MovieClip(url) */
/**
* Creates a MovieClip
object from the specified network URL.
*
* Example: MovieClip movie = new MovieClip(file);
* @param url A network URL containing the movie
*/
public MovieClip(URL url) {
this(url, JTFTools.getURLSuffix(url.toString()));
}
/* Method: play() */
/**
* Starts the movie from its current position.
*
* Example: movie.play();
*/
public void play() {
setLooping(false);
startMovie();
}
/* Method: loop() */
/**
* Plays the movie in a continuous audio loop.
*
* Example: movie.loop();
*/
public void loop() {
setLooping(true);
startMovie();
}
/* Method: stop() */
/**
* Stops the playback of the movie.
*
* Example: movie.stop();
*/
public synchronized void stop() {
stopMovie();
setLooping(false);
}
/* Method: getName() */
/**
* Returns the name of the movie, which is typically the file name from which it
* was read.
*
* Example: String name = movie.getName();
* @return The name of the movie
*/
public String getName() {
return movieName;
}
/* Method: setName(name) */
/**
* Sets a name to identify the movie.
*
* Example: movie.setName(name);
* @param name The name to use for the movie
*/
public void setName(String name) {
movieName = name;
}
/* Method: getFrameCount() */
/**
* Returns the number of frames in a movie.
*
* Example: int nFrames = movie.getFrameCount();
* @return The number of frames in a movie
*/
public int getFrameCount() {
return getQTDuration();
}
/* Method: getFrameRate() */
/**
* Returns the frame rate of the movie.
*
* Example: double frameRate = movie.getFrameRate();
* @return The frame rate of the movie (in frames/second)
*/
public double getFrameRate() {
return getQTTimeScale();
}
/* Method: getDuration() */
/**
* Returns the duration of a movie (in seconds).
*
* Example: double duration = movie.getDuration();
* @return The duration of a movie (in seconds)
*/
public double getDuration() {
return getFrameCount() / getFrameRate();
}
/* Method: getFrameIndex() */
/**
* Returns the current frame index in the movie.
*
* Example: int frameIndex = movie.getFrameIndex();
* @return The current frame index in the movie
*/
public int getFrameIndex() {
return getCurrentTime();
}
/* Method: setFrameIndex(frame) */
/**
* Sets the current frame index.
*
* Example: movie.setFrameIndex(frameIndex);
* @param frameIndex The current frame index in the movie
*/
public void setFrameIndex(int frameIndex) {
setCurrentTime(frameIndex);
}
/* Method: rewind() */
/**
* Rewinds the movie to the beginning. This method is useful after you have
* stopped a movie and want to replay it from the beginning.
*
* Example: movie.rewind();
*/
public void rewind() {
setFrameIndex(0);
}
/* Method: getVolume() */
/**
* Returns the playback volume setting for the movie, which is a number
* between 0 (silent) and 1 (maximum volume).
*
* Example: double volume = movie.getVolume();
* @return The playback volume setting for the movie
*/
public double getVolume() {
return clipVolume;
}
/* Method: setVolume(volume) */
/**
* Sets the playback volume setting for the movie, which is a number
* between 0 (silent) and 1 (maximum volume).
*
* Example: movie.setVolume(volume);
* @param volume The new volume setting for the movie
*/
public void setVolume(double volume) {
clipVolume = volume;
setControllerVolume(volume);
}
/* Method: getPlaybackRate() */
/**
* Returns the playback rate, which is a floating-point number indicating
* the speed and direction of playback. The value 1.0 indicates normal
* speed, 2.0 indicates a movie running at double speed, and 0.5 indicates
* a movie running at half speed. Negative values indicate that the movie
* should run in reverse, so that a playback rate of -1.0 specifies that the
* movie should run backwards at normal speed.
*
* Example: double rate = movie.getPlaybackRate();
* @return The playback rate for the movie
*/
public double getPlaybackRate() {
return clipRate;
}
/* Method: setPlaybackRate(rate) */
/**
* Sets the playback rate for the movie. The value 1.0 indicates normal
* speed, 2.0 indicates a movie running at double speed, and 0.5 indicates
* a movie running at half speed. Negative values indicate that the movie
* should run in reverse, so that a playback rate of -1.0 specifies that the
* movie should run backwards at normal speed.
*
* Example: sound.setPlaybackRate(rate);
* @param rate The new playback rate for the movie
*/
public void setPlaybackRate(double rate) {
clipRate = rate;
if (!isStopped()) setRate(rate);
}
/* Method: enableController() */
/**
* Enables the QuickTime controller displayed at the bottom of the movie. This
* call has no effect if a controller is already enabled.
*
* Example: movie.enableController();
*/
public void enableController() {
if (hasQuickTime) setControllerVisible(true);
}
/* Method: disableController() */
/**
* Disables the QuickTime controller from the bottom of the movie, making it
* disappear from the window. This call has no effect if no controller is
* already disabled.
*
* Example: movie.disableController();
*/
public void disableController() {
if (hasQuickTime) setControllerVisible(false);
}
/* Method: isControllerEnabled() */
/**
* Returns true
if the QuickTime controller is enabled.
*
* Example: if (movie.isControllerEnabled()) . . .
* @return true
if the controller is enabled
*/
public boolean isControllerEnabled() {
return controllerVisible;
}
/* Method: getControllerHeight() */
/**
* Returns the height of the QuickTime controller, measured in pixels. If
* no controller is installed, getControllerHeight
returns 0.
*
* Example: int height = movie.getControllerHeight();
* @return The height of the QuickTime controller (in pixels)
*/
public int getControllerHeight() {
return (controller == null) ? 0 : CONTROLLER_HEIGHT;
}
/**********************************************************************/
/* Overridden methods */
/**********************************************************************/
/* Method: getPreferredSize() */
/**
* Returns the desired height of the movie panel, which is taken from the
* underlying movie, taking account of the controller, if any.
*
* Example: Dimension size = movie.getPreferredSize();
* @return The desired height of the movie panel
*
*/
public Dimension getPreferredSize() {
if (movie == null) return DEFAULT_SIZE;
try {
Method getNaturalBoundsRect = movieClass.getMethod("getNaturalBoundsRect", new Class[0]);
Method getWidth = qdRectClass.getMethod("getWidth", new Class[0]);
Method getHeight = qdRectClass.getMethod("getHeight", new Class[0]);
Object rect = getNaturalBoundsRect.invoke(movie, new Object[0]);
int width = ((Integer) getWidth.invoke(rect, new Object[0])).intValue();
int height = ((Integer) getHeight.invoke(rect, new Object[0])).intValue();
Dimension size = new Dimension(width, height + getControllerHeight());
return size;
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/**********************************************************************/
/* Private methods */
/**********************************************************************/
/* Private constructor: MovieClip(source, name) */
/**
* Constructs a new movie from a data source and assigns the specified name.
*/
private MovieClip(Object source, String name) {
startQuickTime();
movieName = name;
clipVolume = 1.0;
clipRate = 1.0;
setLayout(new BorderLayout());
if (hasQuickTime) {
try {
readMovie(source);
} catch (Exception ex) {
add(new ErrorWindow(name, "Can't read movie file"), BorderLayout.CENTER);
}
} else {
add(new ErrorWindow(name, errorMessage), BorderLayout.CENTER);
}
addComponentListener(new MovieClipListener());
}
/* Private method: startQuickTime() */
/**
* Opens a new QuickTime session and tests to see whether the correct version
* of QuickTime exists.
*/
private void startQuickTime() {
hasQuickTime = false;
try {
movieClass = Class.forName("quicktime.std.movies.Movie");
movieControllerClass = Class.forName("quicktime.std.movies.MovieController");
openMovieFileClass = Class.forName("quicktime.io.OpenMovieFile");
qdRectClass = Class.forName("quicktime.qd.QDRect");
qtDataRefClass = Class.forName("quicktime.std.movies.media.DataRef");
qtFactoryClass = Class.forName("quicktime.app.view.QTFactory");
qtFileClass = Class.forName("quicktime.io.QTFile");
qtSessionClass = Class.forName("quicktime.QTSession");
timeRecordClass = Class.forName("quicktime.std.clocks.TimeRecord");
Method isInitialized = qtSessionClass.getMethod("isInitialized", new Class[0]);
synchronized (lock) {
if (isInitialized.invoke(null, new Object[0]).equals(Boolean.FALSE)) {
Method open = qtSessionClass.getMethod("open", new Class[0]);
open.invoke(null, new Object[0]);
}
}
Method getMajorVersion = qtSessionClass.getMethod("getMajorVersion", new Class[0]);
version = ((Integer) getMajorVersion.invoke(null, new Object[0])).intValue();
if (version < MIN_QUICKTIME_VERSION) {
throw new ErrorException("MovieClip requires QuickTime V7 or later");
}
hasQuickTime = true;
} catch (Exception ex) {
errorMessage = ex.getMessage();
}
}
/* Private method: startMovie() */
/**
* Starts the movie from its current position. This method is identical to
* clicking the play
control in the QuickTime controller.
*/
private void startMovie() {
if (!hasQuickTime || movie == null) return;
try {
Class[] types = { Float.TYPE };
Object[] args = { new Float((float) clipRate) };
Method play = movieControllerClass.getMethod("play", types);
play.invoke(controller, args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: stopMovie() */
/**
* Stops the movie. This method is identical to clicking the
* pause
control in the QuickTime controller.
*/
private void stopMovie() {
if (!hasQuickTime || movie == null) return;
try {
Class[] types = { Float.TYPE };
Object[] args = { new Float(0.0) };
Method play = movieControllerClass.getMethod("play", types);
play.invoke(controller, args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: setLooping(flag) */
/**
* Sets a flag to indicate whether the movie should play in a continuous
* loop.
*/
private void setLooping(boolean flag) {
if (!hasQuickTime || movie == null) return;
try {
Class[] types = { Boolean.TYPE };
Object[] args = { new Boolean(flag) };
Method setLooping = movieControllerClass.getMethod("setLooping", types);
setLooping.invoke(controller, args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: readMovie(source) */
/**
* Reads a QuickTime movie by calling a method appropriate to the source type.
* If the movie is read successfully, this code adds it to the primary container.
*/
private void readMovie(Object source) {
try {
if (source instanceof String) {
String name = (String) source;
if (name.startsWith("http:")) {
try {
movie = readMovieFromURL(new URL(name));
} catch (MalformedURLException ex) {
throw new ErrorException("MovieClip: Malformed URL");
}
} else {
movie = readMovieFromFile(new File(name));
}
} else if (source instanceof File) {
movie = readMovieFromFile((File) source);
} else if (source instanceof URL) {
movie = readMovieFromURL((URL) source);
}
controller = createController();
controllerVisible = true;
addQTComponent(controller);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: createController() */
/**
* Creates a QuickTime controller for the current movie.
*/
private Object createController() throws Exception {
Class[] types = { movie.getClass() };
Object[] args = { movie };
Constructor> movieControllerConstructor = movieControllerClass.getConstructor(types);
return movieControllerConstructor.newInstance(args);
}
/* Private method: addQTComponent(obj) */
/**
* Adds a component to the main container. If the argument is a QuickTime object,
* the code invokes the appropriate asComponent
method to create a
* Java component.
*/
private void addQTComponent(Object obj) throws Exception {
if (!(obj instanceof Component)) {
Class[] types = { obj.getClass() };
Object[] args = { obj };
Method makeQTComponent = qtFactoryClass.getMethod("makeQTComponent", types);
obj = makeQTComponent.invoke(null, args);
if (!(obj instanceof Component)) {
Class> qtClass = obj.getClass();
Method asComponent = qtClass.getMethod("asComponent", new Class[0]);
obj = asComponent.invoke(obj, new Object[0]);
}
}
removeAll();
add((Component) obj, BorderLayout.CENTER);
}
/* Private method: readMovieFromFile(file) */
/**
* Reads a movie from a file object.
*/
private Object readMovieFromFile(File file) {
try {
Class[] types1 = { Class.forName("java.io.File") };
Object[] args1 = { file };
Constructor> qtFileConstructor = qtFileClass.getConstructor(types1);
Object qtFile = qtFileConstructor.newInstance(args1);
Class[] types2 = { qtFileClass };
Object[] args2 = { qtFile };
Method asRead = openMovieFileClass.getMethod("asRead", types2);
Object openMovieFile = asRead.invoke(null, args2);
Class[] types3 = { openMovieFileClass };
Object[] args3 = { openMovieFile };
Method fromFile = movieClass.getMethod("fromFile", types3);
return fromFile.invoke(null, args3);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: readMovieFromURL(href) */
/**
* Reads a movie from the specified URL.
*/
private Object readMovieFromURL(URL url) {
try {
Class[] types1 = { Class.forName("java.lang.String") };
Object[] args1 = { url.toString() };
Constructor> qtDataRefConstructor = qtDataRefClass.getConstructor(types1);
Object qtFile = qtDataRefConstructor.newInstance(args1);
Class[] types2 = { qtDataRefClass, Integer.TYPE };
Object[] args2 = { qtFile, new Integer(0) };
Method fromDataRef = movieClass.getMethod("fromDataRef", types2);
return fromDataRef.invoke(null, args2);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: setControllerVisible(flag) */
/**
* Sets whether the controller is visible.
*/
private void setControllerVisible(boolean flag) {
if (!hasQuickTime || movie == null) return;
if (flag != controllerVisible) {
try {
Class[] types = { Boolean.TYPE };
Object[] args = { new Boolean(flag) };
Method setVisible = movieControllerClass.getMethod("setVisible", types);
setVisible.invoke(controller, args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
controllerVisible = flag;
}
}
/* Private method: getCurrentTime() */
/**
* Returns the movie time as maintained by the controller.
*/
private int getCurrentTime() {
if (!hasQuickTime || movie == null) return 0;
try {
Method getCurrentTime = movieControllerClass.getMethod("getCurrentTime", new Class[0]);
return ((Integer) getCurrentTime.invoke(controller, new Object[0])).intValue();
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: setCurrentTime(time) */
/**
* Sets the movie time. As with the other set
operations, this
* code makes the changes in the controller to ensure that its controls are
* updated.
*/
private void setCurrentTime(int time) {
if (!hasQuickTime || movie == null) return;
try {
Class[] types = { timeRecordClass };
Object[] args = { createTimeRecord(time) };
Method goToTime = movieControllerClass.getMethod("goToTime", types);
goToTime.invoke(controller, args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: setControllerVolume(volume) */
/**
* Sets the controller volume.
*/
private void setControllerVolume(double volume) {
if (!hasQuickTime || movie == null) return;
try {
Class[] types = { Float.TYPE };
Object[] args = { new Float((float) volume) };
Method setVolume = movieControllerClass.getMethod("setVolume", types);
setVolume.invoke(controller, args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: setRate(rate) */
/**
* Sets the movie playback rate.
*/
private void setRate(double volume) {
if (!hasQuickTime || movie == null) return;
try {
Class[] types = { Float.TYPE };
Object[] args = { new Float((float) volume) };
Method setRate = movieClass.getMethod("setRate", types);
setRate.invoke(movie, args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: isStopped() */
/**
* Returns whether the controller is currently stopped.
*/
private boolean isStopped() {
if (!hasQuickTime || movie == null) return true;
try {
Method getPlayRate = movieControllerClass.getMethod("getPlayRate", new Class[0]);
return ((Float) getPlayRate.invoke(controller, new Object[0])).floatValue() == 0.0;
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: getQTDuration() */
/**
* Returns the duration in movie time units.
*/
private int getQTDuration() {
if (!hasQuickTime || movie == null) return 0;
try {
Method getDuration = movieClass.getMethod("getDuration", new Class[0]);
return ((Integer) getDuration.invoke(movie, new Object[0])).intValue();
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: getQTTimeScale() */
/**
* Returns the movie time in units per second.
*/
private int getQTTimeScale() {
if (!hasQuickTime || movie == null) return DEFAULT_TIME_SCALE;
try {
Method getTimeScale = movieClass.getMethod("getTimeScale", new Class[0]);
return ((Integer) getTimeScale.invoke(movie, new Object[0])).intValue();
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private method: createTimeRecord() */
/**
* Creates a TimeRecord
object, which is required in the call
* to goToTime
.
*/
private Object createTimeRecord(int time) {
try {
Class[] types = { Integer.TYPE, Long.TYPE };
Object[] args = { new Integer(getQTTimeScale()), new Long(time) };
Constructor> newTimeRecord = timeRecordClass.getConstructor(types);
return newTimeRecord.newInstance(args);
} catch (Exception ex) {
throw new ErrorException(ex);
}
}
/* Private constants */
private static final int MIN_QUICKTIME_VERSION = 7;
private static final int CONTROLLER_HEIGHT = 16;
private static final int DEFAULT_TIME_SCALE = 600;
private static final Dimension DEFAULT_SIZE = new Dimension(300, 200);
/* Private instance variables */
private Class> movieClass;
private Class> movieControllerClass;
private Class> openMovieFileClass;
private Class> qdRectClass;
private Class> qtDataRefClass;
private Class> qtFactoryClass;
private Class> qtFileClass;
private Class> qtSessionClass;
private Class> timeRecordClass;
private Object controller;
private Object movie;
private String movieName;
private String errorMessage;
private int version;
private boolean hasQuickTime;
private boolean controllerVisible;
private double clipVolume;
private double clipRate;
/* Private class variables */
private static Object lock = new Object();
}
/* Package class: MovieClipListener */
/**
* This class encapsulates the listeners for the movie clip.
*/
class MovieClipListener implements ComponentListener {
public void componentResized(ComponentEvent e) {
((Container) e.getSource()).validate();
}
public void componentHidden(ComponentEvent e) { }
public void componentMoved(ComponentEvent e) { }
public void componentShown(ComponentEvent e) { }
}
/* Package class: ErrorWindow */
/**
* This class is displayed in the movie container if QuickTime does not exist
* on the system or the movie can't be found.
*/
class ErrorWindow extends Component {
/* Constructor: ErrorWindow */
/**
* Constructs an error window, passing in the movie name and the error message string.
*/
public ErrorWindow(String name, String msg) {
movieName = name;
errorMessage = msg;
}
/* Overridden method: paint */
/**
* Paints the name of the movie and the error message.
*/
public void paint(Graphics g) {
Dimension size = getSize();
FontMetrics fm = g.getFontMetrics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, size.width, size.height);
g.setColor(Color.BLACK);
int x = (size.width - fm.stringWidth(movieName)) / 2;
int y = size.height / 2 - fm.getHeight();
g.drawString(movieName, x, y);
g.setColor(Color.RED);
x = (size.width - fm.stringWidth(errorMessage)) / 2;
y += 2 * fm.getHeight();
g.drawString(errorMessage, x, y);
}
/* Private instance variables */
private String movieName;
private String errorMessage;
}