/* * @(#)SEQPlayer.java 1.0.1 2011-08-23 * * Copyright (c) 2010-2011 Werner Randelshofer, Goldau, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package org.monte.media.seq; import org.monte.media.MovieControl; import org.monte.media.ColorCyclePlayer; import org.monte.media.AbstractPlayer; import org.monte.media.gui.JMovieControlAqua; import org.monte.media.gui.ImagePanel; import org.monte.media.image.BitmapImage; import org.monte.media.*; import org.monte.media.io.BoundedRangeInputStream; import org.monte.media.ilbm.ColorCycle; import org.monte.media.ilbm.ColorCyclingMemoryImageSource; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.beans.*; import java.io.*; import java.util.*; import static java.lang.Math.*; import javax.swing.BoundedRangeModel; import javax.swing.DefaultBoundedRangeModel; import javax.swing.event.*; /** * Player for Cyberpaint Sequence animations (*.SEQ). * <p> * Reference:<br> * <a href="http://www.atari-forum.com/wiki/index.php/ST_Picture_Formats" * >http://www.atari-forum.com/wiki/index.php/ST_Picture_Formats</a> * * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland * @version 3.0.2 2011-08-23 Frame duration was too long by one jiffie. * <br>1.0 2010-12-25 Created. */ public class SEQPlayer extends AbstractPlayer implements ColorCyclePlayer { /** * The memory image source handles the image * producer/consumer protocol. */ private ColorCyclingMemoryImageSource memoryImage; /** * Bounded range indicates the number of frames and the * index of the current frame. */ private BoundedRangeModel timeModel; /** * Bounded range indicates the amount of data being * fetched from the data source. */ private BoundedRangeInputStream cachingControlModel; /** * The Input stream containing the movie data. */ private InputStream in; /** * The size of the input file. If the size is not known then * this attribute is set to -1. */ private int inputFileSize = -1; /** * The movie track built from the movie data. */ private SEQMovieTrack track; /** Two bitmaps are needed for double buffering. */ private BitmapImage bitmapEven, bitmapOdd; /** * Index of the frame, that has been prepared * in its even or odd bitmap buffer for display. */ private int preparedEven, preparedOdd; /** * Index of the frame which has been delta * decoded in its even or odd bitmap buffer. */ private int fetchedEven, fetchedOdd; /** * Index of the frame currently being displayed. */ private int displayFrame = -1; /** Indicates wether frames may be skipped or not. */ private boolean isPlayEveryFrame = false; /** Indicates wether playback shall loop or not. */ private volatile boolean isLoop = true; /** Indicates wether the player is in pause mode. */ //private volatile boolean isPaused = true; /** * Jiffies are used be IFF ANIM's for timing. * Jiffies is the number of frames or fields per second. * The variable jiffieMillis is a conversion of Jiffies into milliseconds. */ private float jiffieMillis = 1000f / 60f; /** * Setting the global frame duration overrides all * frame duration settings in the frames of the the movie track. * * Frame Duration in Jiffies. Set this to * -1 if you do not want to override the frame durations in * the frames of the movie track. */ private int globalFrameDuration = -1; /** * The visual component contains the display area * for movie images. */ private ImagePanel /*ImagePanelAWT*/ visualComponent; /** * The visual component contains control elements * for starting and stopping the movie. */ private MovieControl controlComponent; /** * This lock is being used to coordinate the * decoder with the player. */ private Object decoderLock = new Object(); /** * The preferred color model for this player. */ private ColorModel preferredColorModel = null; /** * Indicates wether all data has been cached. * Acts like a latch: Once set to true never changes * its value anymore. */ private volatile boolean isCached = false; /** * The amiga has four audio channels. * There can be only four active audio commands at all times. */ private SEQAudioCommand[] audioChannels = new SEQAudioCommand[4]; /** * Turns audio on or off. */ private boolean isAudioEnabled = true; /** * Determines whether audio is being loaded or not. */ private boolean isLoadAudio; /** */ private boolean debug = false; /** */ private Hashtable properties; /** * This variable is set to true when during decoding of the * input stream at least one audio clip is detected. */ private boolean isAudioAvailable; /** * This variable is set to true when during decoding of the * input stream at least one color cycle is detected. */ private boolean isColorCyclingAvailable; /** Whether color cycling is started. */ private boolean isColorCyclingStarted; /** * Set this to true, if the delta frames of the animation can be decoded * relative to the previous frame and relative to the subsequent frame. */ private boolean isPingPong = true; /** * Direction of the play head: +1 for forward playing, -1 for backward playing. */ private int playDirection = 1; private class Handler implements MouseListener, PropertyChangeListener, ChangeListener { // ------------------------ // MouseListener // ------------------------ @Override public void mouseClicked(MouseEvent event) { if (getState() != CLOSED && event.getModifiers() == InputEvent.BUTTON1_MASK) { if (getState() == STARTED && getTargetState() == STARTED && event.getClickCount() == 1) { stop(); } else if (getState() != STARTED && getTargetState() != STARTED && event.getClickCount() == 2) { start(); } } } @Override public void mouseEntered(MouseEvent event) { } @Override public void mouseExited(MouseEvent event) { } @Override public void mousePressed(MouseEvent event) { } @Override public void mouseReleased(MouseEvent event) { } // ------------------------ // PropertyChangeListener // ------------------------ @Override public void propertyChange(PropertyChangeEvent event) { if (timeModel != null) { int count = track.getFrameCount(); timeModel.setMaximum(count > 0 ? count - 1 : 0); synchronized (decoderLock) { decoderLock.notifyAll(); } } if (event.getPropertyName().equals("audioClipCount")) { setAudioAvailable(track.getAudioClipCount() > 0); } else if (event.getPropertyName().equals("colorCyclesCount")) { setColorCyclingAvailable(track.getColorCyclesCount() > 0); } } // ------------------------ // ChangeListener // ------------------------ @Override public void stateChanged(ChangeEvent evt) { // Time model changed and player in prefetched state? if (evt.getSource() == timeModel) { if (getState() == STARTED) { // Wake the worker thread up. synchronized (this) { notifyAll(); } } else { // Render the video on the worker thread. dispatcher.dispatch( new Runnable() { @Override public void run() { renderVideo(getTimeModel().getValue()); } }); } } } } private Handler handler = new Handler(); public SEQPlayer(InputStream in) { this(in, -1, true); } /** * Creates a new instance. * @param in InputStream containing an IFF ANIM file. * @param inputFileSize The size of the input file. Provide the value -1 * if this is not known. * @param loadAudio Provide value false if this player should not load audio * data. */ public SEQPlayer(InputStream in, int inputFileSize, boolean loadAudio) { this.in = in; this.inputFileSize = inputFileSize; this.isLoadAudio = loadAudio; } /** * Sets the preferred color model. * If this color model is the same as the one used by the * screen device showing the animation, then this may considerably * improve the performance of the player. * Setting this to null will let the player choose a color model * that best suits the media being played. * Calling this method has no effect, if the player is already realized. */ public void setPreferredColorModel(ColorModel cm) { if (bitmapEven == null) { preferredColorModel = cm; } } /** * Returns the bounded range model that represents * the time line of the player. */ @Override public BoundedRangeModel getTimeModel() { return timeModel; } /** * Enables or disables audio playback. */ @Override public void setAudioEnabled(boolean newValue) { boolean oldValue = isAudioEnabled; isAudioEnabled = newValue; propertyChangeSupport.firePropertyChange("audioEnabled", (oldValue) ? Boolean.TRUE : Boolean.FALSE, (newValue) ? Boolean.TRUE : Boolean.FALSE); } /** * Returns true if audio playback is enabled. */ @Override public boolean isAudioEnabled() { return isAudioEnabled; } /** * Swaps left and right speakers if set to true. */ public void setSwapSpeakers(boolean newValue) { boolean oldValue = track.isSwapSpeakers(); track.setSwapSpeakers(newValue); propertyChangeSupport.firePropertyChange("swapSpeakers", (oldValue) ? Boolean.TRUE : Boolean.FALSE, (newValue) ? Boolean.TRUE : Boolean.FALSE); } /** * Returns true if left and right speakers are swapped. */ public boolean isSwapSpeakers() { return track.isSwapSpeakers(); } /** * Returns the bounded range model that represents * the amount of data being fetched from the file * the movie is stored in. */ @Override public BoundedRangeModel getCachingModel() { return cachingControlModel; } /** * Returns the image producer that produces * the animation frames. */ protected ImageProducer getImageProducer() { return memoryImage; } /** * Returns the movie track. */ public SEQMovieTrack getMovieTrack() { return track; } /** * Obtain the display Component for this Player. * The display Component is where visual media is rendered. * If this Player has no visual component, getVisualComponent * returns null. For example, getVisualComponent might return * null if the Player only plays audio. */ @Override public synchronized Component getVisualComponent() { if (visualComponent == null) { visualComponent = /*new ImagePanelAWT()*/ new ImagePanel(); if (getImageProducer() != null) { visualComponent.setImage(visualComponent.getToolkit().createImage(getImageProducer())); } visualComponent.addMouseListener(handler); } return visualComponent; } /** * Obtain the Component that provides the default user * interface for controlling this Player. If this Player * has no default control panel, getControlPanelComponent * returns null. */ @Override public synchronized Component getControlPanelComponent() { if (controlComponent == null) { controlComponent = new JMovieControlAqua(); controlComponent.setPlayer(this); } return controlComponent.getComponent(); } /** * Does the unrealized state. */ @Override protected void doUnrealized() { } /** * Does the realizing state. */ @Override protected void doRealizing() { timeModel = new DefaultBoundedRangeModel(0, 0, 0, 0); timeModel.addChangeListener(handler); cachingControlModel = new BoundedRangeInputStream(in); /*if (inputFileSize != -1) { cachingControlModel.setMaximum(inputFileSize); }*/ track = new SEQMovieTrack(); track.addPropertyChangeListener(handler); // If the components of the player have been created before // we arrived here, then they may not have been initialized properly. // So we reinitialize them here. synchronized (this) { if (controlComponent != null) { controlComponent.setPlayer(this); } } // Decode the file asynchronously. So the player // can play files while they are being decoded. Thread t = new Thread() { @Override public void run() { try { SEQDecoder decoder = new SEQDecoder(cachingControlModel); decoder.produce(track, isLoadAudio); isCached = true; cachingControlModel.setValue(cachingControlModel.getMaximum()); propertyChangeSupport.firePropertyChange("cached", Boolean.FALSE, Boolean.TRUE); //setPaused(false); // No frames in track? Close player. if (track.getFrameCount() == 0) { synchronized (decoderLock) { setTargetState(CLOSED); decoderLock.notifyAll(); } } } catch (Throwable e) { synchronized (decoderLock) { if (visualComponent != null) { visualComponent.setMessage(e.toString()); } setTargetState(CLOSED); decoderLock.notifyAll(); e.printStackTrace(); } } finally { try { in.close(); } catch (IOException e) { } } } }; t.start(); // Wait until enough information has been decoded. // (The player is not realized until at least the // header data of the movie has been decoded.) synchronized (decoderLock) { while (track.getFrameCount() < 1 && getTargetState() != CLOSED) { try { decoderLock.wait(); } catch (InterruptedException e) { } } } // Initialize the player. Needs header information // from the movie track to do this. ColorModel cm; int width = track.getWidth(); int height = track.getHeight(); int nbPlanes = track.getNbPlanes(); int masking = track.getMasking(); if (track.getFrameCount() > 0) { SEQFrame frame = track.getFrame(0); cm = frame.getColorModel(); } else { cm = new DirectColorModel(24, 0xff0000, 0xff00, 0xff); } bitmapEven = new BitmapImage( width, height, nbPlanes + (masking == SEQMovieTrack.MSK_HAS_MASK ? 1 : 0), cm); bitmapOdd = new BitmapImage( width, height, nbPlanes + (masking == SEQMovieTrack.MSK_HAS_MASK ? 1 : 0), cm); //bitmapOdd=bitmapEven; jiffieMillis = 1000f / (float) track.getJiffies(); if (track.getColorCycles().isEmpty()) { bitmapEven.setPreferredChunkyColorModel(preferredColorModel); bitmapOdd.setPreferredChunkyColorModel(preferredColorModel); } /*Hashtable*/ properties = new Hashtable(); properties.put( "aspect", new Double((double) track.getXAspect() / (double) track.getYAspect())); Object comment = track.getProperty("comment"); if (comment != null) { properties.put("comment", comment); } String s; switch (track.getScreenMode()) { case SEQMovieTrack.MODE_INDEXED_COLORS: s = "Indexed Colors"; break; case SEQMovieTrack.MODE_DIRECT_COLORS: s = "Direct Colors"; break; case SEQMovieTrack.MODE_EHB: s = "EHB"; break; case SEQMovieTrack.MODE_HAM6: s = "HAM 6"; break; case SEQMovieTrack.MODE_HAM8: s = "HAM 8"; break; default: s = "unknown"; break; } properties.put("screenMode", s); properties.put("nbPlanes", "" + track.getNbPlanes()); properties.put("jiffies", "" + track.getJiffies()); properties.put("colorCycling", "" + track.getColorCycles().size()); if (bitmapEven.isEnforceDirectColors()) { cm = (preferredColorModel instanceof DirectColorModel) ? preferredColorModel : new DirectColorModel(24, 0xff0000, 0xff00, 0xff); memoryImage = new ColorCyclingMemoryImageSource(width, height, cm, new int[width * height], 0, width, properties); } else if (cm instanceof DirectColorModel) { memoryImage = new ColorCyclingMemoryImageSource(width, height, cm, new int[width * height], 0, width, properties); } else { memoryImage = new ColorCyclingMemoryImageSource(width, height, cm, new byte[width * height], 0, width, properties); if (track.getColorCycles().size() > 0) { for (ColorCycle cc : track.getColorCycles()) { memoryImage.addColorCycle(cc); } if (isColorCyclingStarted()) { memoryImage.start(); } } } memoryImage.setAnimated(true); preparedEven = preparedOdd = Integer.MAX_VALUE; fetchedEven = fetchedOdd = Integer.MAX_VALUE; if (track.getFrameCount() > 0) { renderVideo(0); properties.put("renderMode", bitmapEven.getChunkyColorModel()); } // If the components of the player have been created before // we arrived here, then they may not have been initialized properly. // So we reinitialize them here. synchronized (this) { if (visualComponent != null) { visualComponent.setImage(visualComponent.getToolkit().createImage(getImageProducer())); } } } /** * Does the realized state. */ @Override protected void doRealized() { // Free resources being achieved during prefetch. } /** * Does the prefetching state. */ @Override protected void doPrefetching() { renderVideo(timeModel.getValue()); } /** * Does the prefetched state. */ @Override protected void doPrefetched() { } public void setPlayEveryFrame(boolean newValue) { isPlayEveryFrame = newValue; } /** * Set this to true to treat the two wrapup frames at the end of the * animation like regular frames. * / public void setPlayWrapupFrames(boolean newValue) { track.setPlayWrapupFrames(newValue); int count = track.getFrameCount(); System.out.println("SEQPLayer.setPlayWrapupFrames count="+count); timeModel.setMaximum(count > 0 ? count - 1 : 0); }*/ /** * Set this to true to treat the two wrapup frames at the end of the * animation like regular frames. */ public void setDebug(boolean newValue) { this.debug = newValue; if (newValue == false && visualComponent != null) { visualComponent.setMessage(null); } } /** * Always returns true. */ public boolean isPlayWrapupFrames() { return true; //return track.isPlayWrapupFrames(); } /** * Setting frames per second overrides all * frame duration settings in the frames of the the movie track. * * @param framesPerSecond Frames per section. Set this to * 0f if you do not want to override the frame durations in * the frames of the movie track. */ public void setFramesPerSecond(float framesPerSecond) { if (framesPerSecond <= 0f) { setGlobalFrameDuration(-1); } else { setGlobalFrameDuration((int) (1000f / framesPerSecond)); } } /** * Setting the global frame duration overrides all * frame duration settings in the frames of the the movie track. * * @param frameDuration Frame Duration in milliseconds. Set this to * -1 if you do not want to override the frame durations in * the frames of the movie track. */ public void setGlobalFrameDuration(int frameDuration) { this.globalFrameDuration = frameDuration; } public boolean isPlayEveryFrame() { return isPlayEveryFrame; } public void setLoop(boolean newValue) { boolean oldValue = isLoop; isLoop = newValue; propertyChangeSupport.firePropertyChange("isLoop", oldValue, newValue); } public boolean isLoop() { return isLoop; } public String getDeltaOperationDescription() { String s; int op = track.getDeltaOperation(); switch (op) { case SEQDeltaFrame.OP_Copy: s = "OP Direct"; break; case SEQDeltaFrame.OP_XOR: s = "XOR"; break; default: s = "unknown"; break; } return s + " OP(" + op + ")"; } /** * Does the started state. * Is called by run(). * Does not change the value of targetState but may * change state in case of an error. */ @Override protected void doStarted() { long mediaTime = System.currentTimeMillis() + (long) jiffieMillis; int index; long sleepTime; // Start from beginning when playhead is at end of timeline if (timeModel.getValue() == timeModel.getMaximum()) { timeModel.setValue(timeModel.getMinimum()); } while (getTargetState() == STARTED) { index = timeModel.getValue(); if (isPlayEveryFrame) { if (isAudioEnabled) { prepareAudio(index); } prepareVideo(index); if (mediaTime > System.currentTimeMillis()) { sleepTime = mediaTime - System.currentTimeMillis(); if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (InterruptedException e) { } } } if (globalFrameDuration == -1) { mediaTime = System.currentTimeMillis() + (long) (max(track.getFrameDuration(index),1) * jiffieMillis); } else { mediaTime = System.currentTimeMillis() + globalFrameDuration; } if (isAudioEnabled && !timeModel.getValueIsAdjusting()) { renderAudio(index); } else { muteAudio(); } renderVideo(index); } else { if (mediaTime > System.currentTimeMillis()) { if (isAudioEnabled) { prepareAudio(index); } prepareVideo(index); sleepTime = mediaTime - System.currentTimeMillis(); if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (InterruptedException e) { } } if (globalFrameDuration == -1) { mediaTime += (long) (max(track.getFrameDuration(index),1) * jiffieMillis); } else { mediaTime += (long) globalFrameDuration; } if (isAudioEnabled && !timeModel.getValueIsAdjusting()) { renderAudio(index); } else { muteAudio(); } renderVideo(index); } else { if (isAudioEnabled && !timeModel.getValueIsAdjusting()) { renderAudio(index); } else { muteAudio(); } if (globalFrameDuration == -1) { mediaTime += (long) (max(track.getFrameDuration(index),1) * jiffieMillis); } else { mediaTime += (long) globalFrameDuration; } } } if (!timeModel.getValueIsAdjusting()) { if (timeModel.getValue() == timeModel.getMaximum()) { if (isCached && isLoop && !isPingPong) { timeModel.setValue(timeModel.getMinimum()); } else if (isCached && isPingPong && playDirection == 1) { playDirection = -1; timeModel.setValue(timeModel.getValue() + playDirection); } else { break; } } else if (timeModel.getValue() == timeModel.getMinimum() && isPingPong && playDirection == -1) { playDirection = 1; timeModel.setValue(timeModel.getValue() + playDirection); } else { timeModel.setValue(timeModel.getValue() + playDirection); } } } /*}*/ renderVideo(timeModel.getValue()); muteAudio(); } private void muteAudio() { for (int i = 0; i < audioChannels.length; i++) { if (audioChannels[i] != null) { audioChannels[i].stop(track); audioChannels[i] = null; } } } /** * Closes the player. */ @Override protected void doClosed() { try { in.close(); } catch (IOException e) { } } private void fetchFrame(int index) { SEQFrame frame = null; int fetched; int interleave = track.getInterleave(); BitmapImage bitmap; if (interleave == 1 || (index & 1) == 0) { // even? if (fetchedEven == index) { return; } fetched = fetchedEven; bitmap = bitmapEven; fetchedEven = index; if (fetched == index + interleave && track.getFrame(fetched).isBidirectional()) { frame = (SEQFrame) track.getFrame(fetched); frame.decode(bitmap, track); return; } else { if (fetched > index) { frame = (SEQFrame) track.getFrame(0); frame.decode(bitmap, track); fetched = 0; } } } else { // odd? if (fetchedOdd == index) { return; } fetched = fetchedOdd; bitmap = bitmapOdd; fetchedOdd = index; if (fetched == index + interleave && track.getFrame(fetched).isBidirectional()) { frame = (SEQFrame) track.getFrame(fetched); frame.decode(bitmap, track); return; } else { if (fetched > index) { frame = (SEQFrame) track.getFrame(0); frame.decode(bitmap, track); frame = (SEQFrame) track.getFrame(1); frame.decode(bitmap, track); fetched = 1; } } } for (int i = fetched + interleave; i <= index; i += interleave) { frame = (SEQFrame) track.getFrame(i); frame.decode(bitmap, track); } } /** * Prepare video data for the specified frame index. */ private void prepareVideo(int index) { BitmapImage bitmap; int prepared; int interleave = track.getInterleave(); if (interleave == 1 || (index & 1) == 0) { // even? if (preparedEven == index) { return; } prepared = preparedEven; preparedEven = index; bitmap = bitmapEven; } else { // odd? if (preparedOdd == index) { return; } prepared = preparedOdd; preparedOdd = index; bitmap = bitmapOdd; } // Fetch the frame from the underlying storage system // and decode delta information. fetchFrame(index); // Convert planar to chunky. SEQFrame frame = (SEQFrame) track.getFrame(index); ColorModel cm = frame.getColorModel(); bitmap.setPlanarColorModel(cm); if (prepared == index - interleave && // (bitmap.getPixelType() == BitmapImage.BYTE_PIXEL || // cm == ((SEQFrame) track.getFrame(prepared)).getColorModel())) { bitmap.convertToChunky( frame.getTopBound(track), frame.getLeftBound(track), frame.getBottomBound(track), frame.getRightBound(track)); } else if (isPingPong && prepared == index + interleave &&// (bitmap.getPixelType() == BitmapImage.BYTE_PIXEL || // cm == ((SEQFrame) track.getFrame(prepared)).getColorModel())) { frame = (SEQFrame) track.getFrame(index + interleave); bitmap.convertToChunky( frame.getTopBound(track), frame.getLeftBound(track), frame.getBottomBound(track), frame.getRightBound(track)); } else { bitmap.convertToChunky(); } } /** * Prepare audio data for the specified frame index. */ private void prepareAudio(int index) { SEQFrame frame = (SEQFrame) track.getFrame(index); SEQAudioCommand[] audioCommands = frame.getAudioCommands(); if (audioCommands != null) { for (int i = 0; i < audioCommands.length; i++) { audioCommands[i].prepare(track); } } } /** * Show the video data for the specified frame index. */ private void renderVideo(int index) { if (displayFrame == index) { return; } int interleave = track.getInterleave(); BitmapImage bitmap; if (interleave == 1 || (index & 1) == 0) { // even? bitmap = bitmapEven; } else { // odd? bitmap = bitmapOdd; } prepareVideo(index); ColorModel cm = bitmap.getChunkyColorModel(); if (bitmap.getPixelType() == BitmapImage.INT_PIXEL) { memoryImage.newPixels(bitmap.getIntPixels(), cm, 0, track.getWidth()); } else { memoryImage.newPixels(bitmap.getBytePixels(), cm, 0, track.getWidth()); } displayFrame = index; if (debug && visualComponent != null) { SEQFrame frame = (SEQFrame) track.getFrame(index); StringBuilder buf = new StringBuilder(); buf.append("frame:"); buf.append(index); buf.append(" duration:"); buf.append(frame.getRelTime()); buf.append(", seq op:"); buf.append(frame.getOperation()); SEQAudioCommand[] audioCommands = frame.getAudioCommands(); if (audioCommands != null) { for (int i = 0; i < audioCommands.length; i++) { switch (audioCommands[i].getCommand()) { case SEQAudioCommand.COMMAND_PLAY_SOUND: buf.append("\nplay"); break; case SEQAudioCommand.COMMAND_STOP_SOUND: buf.append("\nstop"); break; case SEQAudioCommand.COMMAND_SET_FREQVOL: buf.append("\nfreqvol"); break; default: buf.append("ILLEGAL COMMAND:"); buf.append(audioCommands[i].getCommand()); break; } buf.append(" sound:"); buf.append(audioCommands[i].getSound()); buf.append(" freq:"); buf.append(audioCommands[i].getFrequency()); buf.append(" vol:"); buf.append(audioCommands[i].getVolume()); buf.append(" channels:"); int channelMask = audioCommands[i].getChannelMask(); boolean first = true; for (int j = 0; j < 4; j++) { if (((1 << j) & channelMask) != 0) { if (!first) { buf.append(", "); } buf.append(j); buf.append((j % 2 == 0) ? "(l)" : "(r)"); first = false; } } } } visualComponent.setMessage(buf.toString()); } } /** * Show the audio data for the specified frame index. */ private synchronized void renderAudio(int index) { prepareAudio(index); // Play audio data if (isActive()) { SEQFrame frame = (SEQFrame) track.getFrame(index); SEQAudioCommand[] audioCommands = frame.getAudioCommands(); if (audioCommands != null) { for (int i = 0; i < audioCommands.length; i++) { audioCommands[i].doCommand(track, audioChannels); } } } } /** * Returns the total duration in milliseconds. */ @Override public long getTotalDuration() { if (globalFrameDuration == -1) { return (long) (track.getTotalDuration() * jiffieMillis); } else { return track.getFrameCount() * globalFrameDuration; } } /** * Returns true when the player has completely cached all movie data. * This player informs all property change listeners, when the value of this * property changes. The name of the property is 'cached'. */ @Override public boolean isCached() { return isCached; } /** Returns true if audio is available. * */ @Override public boolean isAudioAvailable() { return isAudioAvailable; } private void setAudioAvailable(boolean newValue) { boolean oldValue = isAudioAvailable; isAudioAvailable = newValue; propertyChangeSupport.firePropertyChange("audioAvailable", oldValue, newValue); } public void setPingPong(boolean newValue) { boolean oldValue = isPingPong; isPingPong = newValue; if (!newValue) { playDirection = 1; } propertyChangeSupport.firePropertyChange("pingPong", oldValue, newValue); } public boolean isPingPong() { return isPingPong; } private void setColorCyclingAvailable(boolean newValue) { boolean oldValue = isColorCyclingAvailable; isColorCyclingAvailable = newValue; propertyChangeSupport.firePropertyChange("colorCyclingAvailable", oldValue, newValue); } /** Returns true if color cycling is available in the movie track. */ @Override public boolean isColorCyclingStarted() { return isColorCyclingStarted; } /** Starts or stops color cycling. */ @Override public void setColorCyclingStarted(boolean newValue) { boolean oldValue = isColorCyclingStarted; isColorCyclingStarted = newValue; if (memoryImage != null) { memoryImage.setColorCyclingStarted(newValue); propertyChangeSupport.firePropertyChange("colorCyclingStarted", oldValue, newValue); } } /** Starts or stops color cycling. */ @Override public boolean isColorCyclingAvailable() { return isColorCyclingAvailable; } /** Sets whether colors are blended during color cycling. */ @Override public void setBlendedColorCycling(boolean newValue) { if (memoryImage != null) { boolean oldValue = memoryImage.isBlendedColorCycling(); memoryImage.setBlendedColorCycling(newValue); propertyChangeSupport.firePropertyChange("blendedColorCycling", oldValue, newValue); } } /** Returns true if colors are blended during color cycling. */ @Override public boolean isBlendedColorCycling() { return memoryImage == null ? false : memoryImage.isBlendedColorCycling(); } }