/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
/*
* The org.opensourcephysics.media.core package defines the Open Source Physics
* media framework for working with video and other media.
*
* Copyright (c) 2014 Douglas Brown and Wolfgang Christian.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* For additional information and documentation on Open Source Physics,
* please see <http://www.opensourcephysics.org/>.
*/
package org.opensourcephysics.media.core;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import javax.swing.SwingUtilities;
import org.opensourcephysics.controls.XML;
/**
* This is a ClipControl that displays every step in a video clip.
*
* @author Douglas Brown
* @version 1.0
*/
public class StepperClipControl extends ClipControl {
// instance fields
private javax.swing.Timer timer;
private double frameDuration = 100; // milliseconds
private boolean playing = false;
private boolean readyToStep = true;
private boolean stepDisplayed = true;
private int minDelay = 10; // milliseconds
private int maxDelay = 5000; // milliseconds
/**
* Constructs a StepperClipControl object.
*
* @param videoClip the video clip
*/
protected StepperClipControl(VideoClip videoClip) {
super(videoClip);
videoClip.addPropertyChangeListener(this);
if(video!=null) {
video.addPropertyChangeListener(this);
if(video.getFrameCount()>1 && video.getDuration()>0) {
double ti = video.getFrameTime(video.getStartFrameNumber());
double tf = video.getFrameTime(video.getEndFrameNumber());
int count = video.getEndFrameNumber()-video.getStartFrameNumber();
if((count!=0)&&(tf-ti)>0) {
frameDuration = (int) (tf-ti)/count;
}
}
}
// int delay = (int) (frameDuration*clip.getStepSize());
// delay = Math.min(delay, maxDelay);
// delay = Math.max(delay, minDelay);
timer = new javax.swing.Timer(getTimerDelay(), new ActionListener() {
public void actionPerformed(ActionEvent e) {
readyToStep = true;
step();
}
});
timer.setRepeats(false);
// set coalesce to false to avoid combining events when trying to go too fast
timer.setCoalesce(false);
}
/**
* Plays the clip.
*/
public void play() {
if(clip.getStepCount()==1) {
return;
}
playing = true;
readyToStep = true;
if(stepNumber==clip.getStepCount()-1) {
setStepNumber(0);
} else {
step();
}
support.firePropertyChange("playing", null, new Boolean(true)); //$NON-NLS-1$
}
/**
* Stops at the next step.
*/
public void stop() {
timer.stop();
readyToStep = true;
stepDisplayed = true;
playing = false;
support.firePropertyChange("playing", null, new Boolean(false)); //$NON-NLS-1$
}
/**
* Steps forward one step.
*/
public void step() {
if((stepNumber>=clip.getStepCount()-1)&&!looping) {
stop();
} else if(stepDisplayed&&(!playing||readyToStep)) {
stepDisplayed = false;
if(playing) {
readyToStep = false;
timer.restart();
}
if(stepNumber<clip.getStepCount()-1) {
setStepNumber(stepNumber+1);
} else if(looping) {
setStepNumber(0);
}
}
}
/**
* Steps back one step.
*/
public void back() {
if(stepDisplayed&&(stepNumber>0)) {
stepDisplayed = false;
setStepNumber(stepNumber-1);
}
}
/**
* Sets the step number.
*
* @param n the desired step number
*/
public void setStepNumber(int n) {
n = Math.max(0, n);
n = Math.min(clip.getStepCount()-1, n);
if(n==stepNumber && clip.stepToFrame(n)==getFrameNumber()) {
return;
}
if (video==null) {
super.setStepNumber(n);
stepDisplayed = true;
support.firePropertyChange("stepnumber", null, new Integer(n)); //$NON-NLS-1$
}
else {
int end = video.getEndFrameNumber();
final int m = clip.stepToFrame(n)+clip.getFrameShift();
final int stepNum = n;
if (m>end) {
super.setStepNumber(n);
video.setVisible(false);
stepDisplayed = true;
support.firePropertyChange("stepnumber", null, new Integer(n)); //$NON-NLS-1$
}
else {
video.setVisible(videoVisible);
Runnable runner = new Runnable() {
public void run() {
if (videoFrameNumber==m) {
stepDisplayed = true;
}
else {
if (video.getFrameNumber()==m) { // setting frame number will have no effect
StepperClipControl.super.setStepNumber(stepNum);
stepDisplayed = true;
support.firePropertyChange("stepnumber", null, new Integer(stepNum)); //$NON-NLS-1$
}
else {
video.setFrameNumber(m);
}
}
}
};
SwingUtilities.invokeLater(runner);
}
}
}
/**
* Sets the play rate.
*
* @param newRate the desired rate
*/
public void setRate(double newRate) {
if((newRate==0)||(newRate==rate)) {
return;
}
rate = Math.abs(newRate);
// if (video==null) {
// int delay = (int) (getMeanFrameDuration()*clip.getStepSize()/rate);
// delay = Math.min(delay, maxDelay);
// delay = Math.max(delay, minDelay);
timer.setInitialDelay(getTimerDelay());
// }
support.firePropertyChange("rate", null, new Double(rate)); //$NON-NLS-1$
}
/**
* Gets the average frame duration in milliseconds (for calculations).
*
* @return the frame duration in milliseconds
*/
public double getMeanFrameDuration() {
if (video!=null && video.getDuration()>0) {
int count = video.getEndFrameNumber()-video.getStartFrameNumber();
if(count!=0) {
double ti = video.getFrameTime(video.getStartFrameNumber());
double tf = video.getFrameTime(video.getEndFrameNumber());
return timeStretch*(tf-ti)/count;
}
return timeStretch*video.getDuration()/video.getFrameCount();
}
return frameDuration;
}
/**
* Sets the frame duration.
*
* @param duration the desired frame duration in milliseconds
*/
public void setFrameDuration(double duration) {
duration = Math.abs(duration);
if(duration==0 || duration==getMeanFrameDuration()) {
return;
}
if (video!=null && video instanceof ImageVideo) {
ImageVideo iVideo = (ImageVideo)video;
iVideo.setFrameDuration(duration);
frameDuration = duration;
timer.setInitialDelay(getTimerDelay());
}
else if (video!=null && video.getDuration()>0) {
double ti = video.getFrameTime(video.getStartFrameNumber());
double tf = video.getFrameTime(video.getEndFrameNumber());
int count = video.getEndFrameNumber()-video.getStartFrameNumber();
if(count!=0) {
timeStretch = duration*count/(tf-ti);
}
}
else {
frameDuration = duration;
timer.setInitialDelay(getTimerDelay());
}
support.firePropertyChange("frameduration", null, new Double(duration)); //$NON-NLS-1$
}
/**
* Turns on/off looping.
*
* @param loops <code>true</code> to turn looping on
*/
public void setLooping(boolean loops) {
if(loops==looping) {
return;
}
looping = loops;
support.firePropertyChange("looping", null, new Boolean(loops)); //$NON-NLS-1$
}
/**
* Gets the playing status.
*
* @return <code>true</code> if playing
*/
public boolean isPlaying() {
return playing;
}
/**
* Gets the current time in milliseconds measured from step 0.
*
* @return the current time
*/
public double getTime() {
if (video!=null && video.getDuration()>0) {
int n = video.getFrameNumber();
double videoTime = video.getFrameTime(n);
int m = clip.stepToFrame(getStepNumber())+clip.getFrameShift();
if (m>video.getFrameCount()-1) {
int extra = m-video.getFrameCount()+1;
videoTime = video.getFrameTime(video.getFrameCount()-1)+extra*frameDuration;
}
return(videoTime-video.getStartTime())*timeStretch;
}
return stepNumber*frameDuration*clip.getStepSize();
}
/**
* Gets the start time of the specified step measured from step 0.
*
* @param stepNumber the step number
* @return the step time
*/
public double getStepTime(int stepNumber) {
if (video!=null && video.getDuration()>0) {
int n = clip.stepToFrame(stepNumber)+clip.getFrameShift();
double videoTime = video.getFrameTime(n);
if (n>video.getFrameCount()-1) {
int extra = n-video.getFrameCount()+1;
videoTime = video.getFrameTime(video.getFrameCount()-1)+extra*frameDuration;
}
return (videoTime-video.getStartTime())*timeStretch;
}
return stepNumber*frameDuration*clip.getStepSize();
}
/**
* Responds to property change events.
*
* @param e the property change event
*/
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if(name.equals("stepsize")) { // from video clip //$NON-NLS-1$
timer.setInitialDelay(getTimerDelay());
}
else if(name.equals("framenumber")) { // from video //$NON-NLS-1$
int n = ((Integer) e.getNewValue()).intValue();
stepDisplayed = true;
if(n!=videoFrameNumber) {
super.setFrameNumber(n-clip.getFrameShift());
support.firePropertyChange("stepnumber", null, new Integer(stepNumber)); //$NON-NLS-1$
}
if(playing) {
step();
}
}
else super.propertyChange(e);
}
/**
* Gets the timer delay.
*
* @return the timer delay in milliseconds
*/
private int getTimerDelay() {
double duration = frameDuration;
if (video!=null && video.getDuration()>0) {
int count = video.getEndFrameNumber()-video.getStartFrameNumber();
if(count!=0) {
double ti = video.getFrameTime(video.getStartFrameNumber());
double tf = video.getFrameTime(video.getEndFrameNumber());
duration = (tf-ti)/count;
}
else duration = video.getDuration()/video.getFrameCount();
}
int delay = (int) (duration*clip.getStepSize()/rate);
delay = Math.min(delay, maxDelay);
delay = Math.max(delay, minDelay);
return delay;
}
/**
* Returns an XML.ObjectLoader to save and load data for this class.
*
* @return the object loader
*/
public static XML.ObjectLoader getLoader() {
return new Loader();
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/