All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.fmj.media.BasicController Maven / Gradle / Ivy

There is a newer version: 1.0.2-jitsi
Show newest version
package net.sf.fmj.media;

import java.util.*;

import javax.media.*;

import net.sf.fmj.media.util.*;

/**
 * Media Controller implements the basic functionalities of a
 * java.media.Controller. These include:<
 * 
    *
  • The clock calculations using the BasicClock helper class. *
  • The RealizeWorkThread and PrefetchWorkThread to implement realize() and * prefetch() in the correct unblocking manner. *
  • The ListenerList to maintain the list of ControllerListener. *
  • Two ThreadedEventQueues for incoming and outgoing ControllerEvents. *
*

* * @version 1.9, 98/11/19 */ public abstract class BasicController implements Controller, Duration { private int targetState = Unrealized; protected int state = Unrealized; private List listenerList = null; private SendEventQueue sendEvtQueue; private ConfigureWorkThread configureThread = null; private RealizeWorkThread realizeThread = null; private PrefetchWorkThread prefetchThread = null; protected String processError = null; private Clock clock; // Use the BasicClock to keep track of time // and for some calculations. private TimedStartThread startThread = null; private StopTimeThread stopTimeThread = null; private boolean interrupted = false; private Object interruptSync = new Object(); final static int Configuring = Processor.Configuring; final static int Configured = Processor.Configured; protected boolean stopThreadEnabled = true; /** * Set the timebase used by the controller. i.e. all media-time to * time-base-time will be computed with the given time base. The subclass * should implement and further specialized this method to do the real work. * But it should also invoke this method to maintain the correct states. * * @param tb * the time base to set to. * @exception IncompatibleTimeBaseException * is thrown if the Controller cannot accept the given time * base. */ static String TimeBaseError = "Cannot set time base on an unrealized controller."; /** * Start the controller. Invoke clock.start() to maintain the clock states. * It starts a wait thread to wait for the given tbt. At tbt, it will wake * up and call doStart(). A subclass should implement the doStart() method * to do the real work. * * @param tbt * the timebase time to start the controller. */ static String SyncStartError = "Cannot start the controller before it has been prefetched."; /** * Set the stop time. Invoke clock.setStopTime() to maintain the clock * states. The subclass should implement and further specialized this method * to do the real work. But it should also invoke this method to maintain * the correct states. * * @param t * the time to stop. */ static String StopTimeError = "Cannot set stop time on an unrealized controller."; /** * Set the media time. Invoke clock.setMediaTime() to maintain the clock * states. The subclass should implement and further specialized this method * to do the real work. But it should also invoke this method to maintain * the correct states. * * @param now * the media time to set to. */ static String MediaTimeError = "Cannot set media time on a unrealized controller"; /** * Get the current time base. * * @return the current time base. */ static String GetTimeBaseError = "Cannot get Time Base from an unrealized controller"; /** * Set the rate of presentation: 1.0: normal, 2.0: twice the speed. -2.0: * twice the speed in reverse. Note that not all rates are supported. * Invokes clock.setRate() to maintain the clock states. The subclass * SHOULDN'T in general override this class. If necessary, it should * override the behavior using the doSetRate method. By overriding the * doSetRate method, subclass Conrollers can support negative rates, * fractional rates etc., but they should guard against illegal rates from * going into the clock calculations. * * @param factor * the rate to set to. * @return the actual rate used. */ static String SetRateError = "Cannot set rate on an unrealized controller."; /** * Returns the start latency. Don't know until the particular node is * implemented. * * @return the start latency. */ static String LatencyError = "Cannot get start latency from an unrealized controller"; /** * Release the resouces held by the controller. It obeys strict rules as * specified in the spec. Implement the abortRealize and abortPrefetch * methods to actually do the work. * * This is a blocking call. It returns only when the Controller has done * deallocating the resources. This should be called from an external thread * outside of the controller. Take caution if this is being call from inside * the Controller. It may cause deadlock. */ static String DeallocateError = "deallocate cannot be used on a started controller."; public BasicController() { sendEvtQueue = new SendEventQueue(this); sendEvtQueue.setName(sendEvtQueue.getName() + ": SendEventQueue: " + this.getClass().getName()); sendEvtQueue.start(); clock = new BasicClock(); } /** * Called when the configure() is aborted, i.e. deallocate() was called * while realizing. Release all resources claimed previously by the * configure() call. Override this to implement subclass behavior. */ protected void abortConfigure() { } /** * Called when the prefetch() is aborted, i.e. deallocate() was called while * prefetching. Release all resources claimed previously by the prefetch * call. Override this to implement subclass behavior. */ protected abstract void abortPrefetch(); /** * Called when the realize() is aborted, i.e. deallocate() was called while * realizing. Release all resources claimed previously by the realize() * call. Override this to implement subclass behavior. */ protected abstract void abortRealize(); // Return true if the stop time has already passed private boolean activateStopThread(long timeToStop) { if (getStopTime().getNanoseconds() == Long.MAX_VALUE) return false; if (stopTimeThread != null && stopTimeThread.isAlive()) { stopTimeThread.abort(); stopTimeThread = null; } if (timeToStop > 100000000) { (stopTimeThread = new StopTimeThread(this, timeToStop)).start(); return false; } else { return true; } } /** * Add a listener to the listenerList. This listener will be notified of * this controller's event. This needs to be a synchronized method so as to * maintain the integrity of the listenerList. */ final public void addControllerListener(ControllerListener listener) { if (listenerList == null) listenerList = new ArrayList(); synchronized (listenerList) { if (!listenerList.contains(listener)) listenerList.add(listener); } } // Return timeToStop private long checkStopTime() { long stopTime = getStopTime().getNanoseconds(); if (stopTime == Long.MAX_VALUE) return 1; return (long) ((stopTime - getMediaTime().getNanoseconds()) / getRate()); } /** * A subclass of this implement close to stop all threads to make it * "finalizable", i.e., ready to be garbage collected. */ final public void close() { doClose(); // Interrupt the controller. If it's in any of the state // transition threads, the threads will be interrupted and // that transition will be aborted. interrupt(); if (startThread != null) startThread.abort(); if (stopTimeThread != null) stopTimeThread.abort(); if (sendEvtQueue != null) { sendEvtQueue.kill(); sendEvtQueue = null; } } /** * Called when the controller is realized and when all the * ConfigureCompleteEvents from down stream Controllers have been received. * If a subclass wants to override this method, it should still invoke this * to ensure the correct events being sent to the upstream Controllers. */ protected synchronized void completeConfigure() { state = Configured; sendEvent(new ConfigureCompleteEvent(this, Configuring, Configured, getTargetState())); if (getTargetState() >= Realized) { realize(); } } /** * Called when the controller is prefetched and when all the * PrefetchCompleteEvents from down stream nodes have been received. If a * subclass wants to override this method, it should still invoke this to * ensure the correct events being sent to the upstream Controllers. */ protected/* synchronized */void completePrefetch() { // Send back the events to whoever is listening, most likely the // upstream nodes. clock.stop(); state = Prefetched; sendEvent(new PrefetchCompleteEvent(this, Prefetching, Prefetched, getTargetState())); } /** * Called when the controller is realized and when all the * RealizeCompleteEvents from down stream Controllers have been received. If * a subclass wants to override this method, it should still invoke this to * ensure the correct events being sent to the upstream Controllers. */ protected synchronized void completeRealize() { // Send back the events to whoever is listening, most likely the // upstream nodes. state = Realized; sendEvent(new RealizeCompleteEvent(this, Realizing, Realized, getTargetState())); if (getTargetState() >= Prefetched) { prefetch(); } } public synchronized void configure() { if (getTargetState() < Configured) setTargetState(Configured); switch (state) { case Configured: case Realizing: case Realized: case Prefetching: case Prefetched: case Started: sendEvent(new ConfigureCompleteEvent(this, state, state, getTargetState())); break; case Configuring: break; case Unrealized: state = Configuring; sendEvent(new TransitionEvent(this, Unrealized, Configuring, getTargetState())); configureThread = new ConfigureWorkThread(this); configureThread.setName(configureThread.getName() + "[ " + this + " ]" + " ( configureThread)"); configureThread.start(); } } final public void deallocate() { int previousState = getState(); // It's illegal to use deallocate on a started controller. if (state == Started) { throwError(new ClockStartedError(DeallocateError)); } // stop the thread even if isAlive() is false as // we want to kill the thread that has been created // but not scheduled to run switch (state) { case Configuring: case Realizing: interrupt(); state = Unrealized; break; case Prefetching: interrupt(); state = Realized; break; case Prefetched: abortPrefetch(); state = Realized; resetInterrupt(); break; } setTargetState(state); // Use by subclass to add in its own behavior. doDeallocate(); // Wait for the interrupt to take effect. synchronized (interruptSync) { while (isInterrupted()) { try { interruptSync.wait(); } catch (InterruptedException e) { } } } sendEvent(new DeallocateEvent(this, previousState, state, state, getMediaTime())); } /** * An internal function to notify the listeners on the listener list the * given event. This gets called by the sendEvtQueue's processEvent() * callback. This method updates a lock on the Vector listenerList before * enumerating it. */ final protected void dispatchEvent(ControllerEvent evt) { if (listenerList == null) return; synchronized (listenerList) { for (ControllerListener listener : listenerList) listener.controllerUpdate(evt); } } /** * Invoked by close() to cleanup the Controller. Override this to implement * subclass behavior. */ protected void doClose() { } /** * The stub function to perform the steps to configure the controller. Call * configure() for the public method. */ protected boolean doConfigure() { return true; } /** * Called by deallocate(). Subclasses should implement this for its specific * behavior. */ protected void doDeallocate() { } /** * Called when realize() has failed. */ protected void doFailedConfigure() { state = Unrealized; setTargetState(Unrealized); String msg = "Failed to configure"; if (processError != null) msg = msg + ": " + processError; sendEvent(new ResourceUnavailableEvent(this, msg)); processError = null; } /** * Called when the prefetch() has failed. */ protected void doFailedPrefetch() { state = Realized; setTargetState(Realized); String msg = "Failed to prefetch"; if (processError != null) msg = msg + ": " + processError; sendEvent(new ResourceUnavailableEvent(this, msg)); processError = null; } /** * Called when realize() has failed. */ protected void doFailedRealize() { state = Unrealized; setTargetState(Unrealized); String msg = "Failed to realize"; if (processError != null) msg = msg + ": " + processError; sendEvent(new ResourceUnavailableEvent(this, msg)); processError = null; } /** * The stub function to perform the steps to prefetch the controller. Call * prefetch for the public method. This is called from a separately running * thread. So do take necessary precautions to protect shared resources. * It's OK to put an empty stub function body here. *

* Return true if the prefetch is successful. Return false and set the * processError string if failed. *

* This function is not declared synchronized because first it is already * guaranteed by realize() not to be called more than once simultaneously. * Secondly if this is synchronized, then other synchronized methods, * deallocate() and processEvent() will be blocked since they are * synchronized methods. Override this to implement subclass behavior. * * @return true if successful. */ protected abstract boolean doPrefetch(); /** * The stub function to perform the steps to realize the controller. Call * realize() for the public method. This is called from a separately running * thread. So do take necessary precautions to protect shared resources. * It's OK to put an empty stub function body here. *

* Return true if the realize is successful. Return false and set the * processError string if failed. *

* This function is not declared synchronized because first it is already * guaranteed by realize() not to be called more than once simultaneously. * Secondly if this is synchronized, then other synchronized methods, * deallocate() and processEvent() will be blocked since they are * synchronized methods. Override this to implement subclass behavior. * * @return true if successful. */ protected abstract boolean doRealize(); protected void doSetMediaTime(Time when) { } // Override this to implement subclass behavior. // Conrollers can override this method if they // can support negative rates, and/or have other // other restrictions. protected float doSetRate(float factor) { return factor; } /** * Start immediately. Invoked from start(tbt) when the scheduled start time * is reached. Use the public start(tbt) method for the public interface. * Override this to implement subclass behavior. */ protected abstract void doStart(); /** * Invoked from stop(). Override this to implement subclass behavior. */ protected void doStop() { } protected Clock getClock() { return clock; } /** * Get the Control that supports the class or interface specified. * The full class or interface name should be specified. Null is * returned if the Control is not supported. * * @return Control for the class or interface name. */ public Control getControl(String type) { Class cls; try { cls = Class.forName(type); } catch (ClassNotFoundException e) { return null; } Control cs[] = getControls(); for (int i = 0; i < cs.length; i++) { if (cls.isInstance(cs[i])) return cs[i]; } return null; } /** * Return a list of Control objects this Controller supports. * If there are no controls, then an array of length zero is returned. * * @return list of Controller controls. */ public Control[] getControls() { // Not implemented $$$ // Is this correct ? $$$ return new Control[0]; } /** * Return the duration of the media. It's unknown until we implement a * particular node. * * @return the duration of the media. */ public Time getDuration() { return Duration.DURATION_UNKNOWN; } /** * Get the current media time in nanoseconds. * * @return the media time in nanoseconds. */ public long getMediaNanoseconds() { return clock.getMediaNanoseconds(); } /** * Return the current media time. Uses the clock to do the computation. A * subclass can override this method to do the right thing for itself. * * @return the current media time. */ public Time getMediaTime() { return clock.getMediaTime(); } /** * Get the current presentation speed. * * @return the current presentation speed. */ public float getRate() { return clock.getRate(); } public Time getStartLatency() { if (state < Realized) { throwError(new NotRealizedError(LatencyError)); } return LATENCY_UNKNOWN; } /** * Get the current state of the controller. * * @return the current state of the controller. */ final public int getState() { return state; } /** * Get the preset stop time. * * @return the preset stop time. */ public Time getStopTime() { return clock.getStopTime(); } /** * Return the Sync Time. Not yet implementated. */ public Time getSyncTime() { return new Time(0); } /** * Get the current target state. * * @return the current target state. */ final public int getTargetState() { return targetState; } public TimeBase getTimeBase() { if (state < Realized) { throwError(new NotRealizedError(GetTimeBaseError)); } return clock.getTimeBase(); } /** * Interrupt the process. */ protected void interrupt() { synchronized (interruptSync) { interrupted = true; interruptSync.notify(); } } /** * Subclass should define this. If this return true, the controller will go * through the Configured state. For example, the Controller implementing * the Processor should return true. */ protected abstract boolean isConfigurable(); /** * Return true if the process is interrupted. */ protected boolean isInterrupted() { return interrupted; } /** * Map the given media-time to time-base-time. * * @param t * given media time. * @return timebase time. * @exception ClockStoppedException * thrown if the Controller has already been stopped. */ public Time mapToTimeBase(Time t) throws ClockStoppedException { return clock.mapToTimeBase(t); } /** * Take the necessary steps to prefetch the controller. This is a * non-blocking call. It starts a work thread to do the real work. The * actual code to do the realizing should be written in doPrefetch(). The * thread is also responsible for catching all the PrefetchCompleteEvents * from the down stream nodes. When the steps to prefetch the controller are * completed and when all the PrefetchCompleteEvents from down stream nodes * have been received, the completePrefetch() call will be invoked. */ public final/* synchronized */void prefetch() { if (getTargetState() <= Realized) setTargetState(Prefetched); switch (state) { case Prefetched: case Started: sendEvent(new PrefetchCompleteEvent(this, state, state, getTargetState())); break; case Configuring: case Realizing: case Prefetching: break; // $$ Nothing is done. case Unrealized: case Configured: // The controller is not realized yet, we have to implicitly // carry out a realize(). realize(); break; case Realized: // Put it in the prefetching state. // synchronized(this) { state = Prefetching; sendEvent(new TransitionEvent(this, Realized, Prefetching, getTargetState())); // Start the prefetch thread for this controller. prefetchThread = new PrefetchWorkThread(this); prefetchThread.setName(prefetchThread.getName() + " ( prefetchThread)"); prefetchThread.start(); break; } } /** * Take the necessary steps to realize the controller. This is a * non-blocking call. It starts a work thread to do the real work. The * actual code to do the realizing should be written in doRealize(). The * thread is also responsible for catching all the RealizeCompleteEvents * from the down stream nodes. When the steps to realize the controller are * completed and when all the RealizeCompleteEvents from down stream nodes * have been received, the completeRealize() call will be invoked. */ public final synchronized void realize() { if (getTargetState() < Realized) setTargetState(Realized); switch (state) { case Realized: case Prefetching: case Prefetched: case Started: sendEvent(new RealizeCompleteEvent(this, state, state, getTargetState())); break; case Realizing: case Configuring: break; // $$ Nothing is done. Two realize() will result in one event case Unrealized: // For processors, we'll implicitly call configure. if (isConfigurable()) { configure(); break; } case Configured: // Put it in the realizing state. int oldState = state; state = Realizing; sendEvent(new TransitionEvent(this, oldState, Realizing, getTargetState())); // Start the realize thread for this controller. realizeThread = new RealizeWorkThread(this); realizeThread.setName(realizeThread.getName() + "[ " + this + " ]" + " ( realizeThread)"); realizeThread.start(); break; } } /** * Remove a listener from the listener list. The listener will stop * receiving notification from this controller. This needs to be a * synchronized method so as to maintain the integrity of the listenerList. */ final public void removeControllerListener(ControllerListener listener) { if (listenerList == null) return; synchronized (listenerList) { listenerList.remove(listener); } } /** * Reset the interrupted flag. */ protected void resetInterrupt() { synchronized (interruptSync) { interrupted = false; interruptSync.notify(); } } /** * Send an event to the listeners listening to my events. The event is * Queued in the sendEvtQueue which runs in a separate thread. This way, * sendEvent() won't be blocked. */ final protected void sendEvent(ControllerEvent evt) { if (sendEvtQueue != null) sendEvtQueue.postEvent(evt); } /** * Subclass can use this to switch to a different default clock. */ protected void setClock(Clock c) { clock = c; } protected void setMediaLength(long t) { if (clock instanceof BasicClock) ((BasicClock) clock).setMediaLength(t); } public void setMediaTime(Time when) { if (state < Realized) { throwError(new NotRealizedError(MediaTimeError)); } clock.setMediaTime(when); doSetMediaTime(when); sendEvent(new MediaTimeSetEvent(this, when)); } public float setRate(float factor) { if (state < Realized) { throwError(new NotRealizedError(SetRateError)); } float oldRate = getRate(); float rateSet = doSetRate(factor); float newRate = clock.setRate(rateSet); if (newRate != oldRate) { sendEvent(new RateChangeEvent(this, newRate)); } return newRate; } public void setStopTime(Time t) { if (state < Realized) { throwError(new NotRealizedError(StopTimeError)); } Time oldStopTime = getStopTime(); clock.setStopTime(t); boolean stopTimeHasPassed = false; if (state == Started) { long timeToStop; if (((timeToStop = checkStopTime()) < 0) || (stopThreadEnabled && activateStopThread(timeToStop))) stopTimeHasPassed = true; } if (oldStopTime.getNanoseconds() != t.getNanoseconds()) { sendEvent(new StopTimeChangeEvent(this, t)); } if (stopTimeHasPassed) { stopAtTime(); } } /** * Set the target state. */ final protected/* synchronized */void setTargetState(int state) { targetState = state; } public void setTimeBase(TimeBase tb) throws IncompatibleTimeBaseException { if (state < Realized) { throwError(new NotRealizedError(TimeBaseError)); } clock.setTimeBase(tb); } /** * Stop the controller. Invoke clock.stop() to maintain the clock states. * The subclass should implement and further specialized this method to do * the real work. But it should also invoke this method to maintain the * correct states. */ public void stop() { if (state == Started || state == Prefetching) { stopControllerOnly(); doStop(); } } /** * Stop because stop time has been reached. Subclasses should override this * method. */ protected void stopAtTime() { stop(); setStopTime(Clock.RESET); sendEvent(new StopAtTimeEvent(this, Started, Prefetched, getTargetState(), getMediaTime())); } /** * Stop the controller. Invoke clock.stop() to maintain the clock states. * The subclass should implement and further specialized this method to do * the real work. But it should also invoke this method to maintain the * correct states. */ protected void stopControllerOnly() { if (state == Started || state == Prefetching) { clock.stop(); state = Prefetched; setTargetState(Prefetched); if (stopTimeThread != null && stopTimeThread.isAlive() && Thread.currentThread() != stopTimeThread) { stopTimeThread.abort(); } // If start(tbt) was invoked and it hasn't reached the // scheduled tbt yet, the startThread is spinning and // we need to abort that. if (startThread != null && startThread.isAlive()) startThread.abort(); } } public void syncStart(final Time tbt) { if (state < Prefetched) { throwError(new NotPrefetchedError(SyncStartError)); } clock.syncStart(tbt); // Will generate ClockStartedError if state is // Started state = Started; setTargetState(Started); sendEvent(new StartEvent(this, Prefetched, Started, Started, getMediaTime(), tbt)); long timeToStop; if ((timeToStop = checkStopTime()) < 0 || (stopThreadEnabled && activateStopThread(timeToStop))) { // If the stop-time is set to a value that the Clock // has already passed, the Clock immediately stops. stopAtTime(); return; } // Schedule the start time. // startThread will wake up at the scheduled tbt and call the // protected doStart() method. startThread = new TimedStartThread(this, tbt.getNanoseconds()); startThread.setName(startThread.getName() + " ( startThread: " + this + " )"); startThread.start(); } protected boolean syncStartInProgress() { return (startThread != null && startThread.isAlive()); } protected void throwError(Error e) { Log.dumpStack(e); throw e; } } /** * A Thread to take care of realizing and catching of ConfigureCompleteEvents * from down stream nodes. */ class ConfigureWorkThread extends StateTransitionWorkThread { // Made this method on package private class public [for jdk1.2 security] public ConfigureWorkThread(BasicController mc) { controller = mc; setName(getName() + ": " + mc); } @Override protected void aborted() { controller.abortConfigure(); } @Override protected void completed() { controller.completeConfigure(); } @Override protected void failed() { controller.doFailedConfigure(); } @Override protected boolean process() { return controller.doConfigure(); } } /** * A Thread to take care of prefetching and catching of PrefetchCompleteEvents * from down stream nodes. */ class PrefetchWorkThread extends StateTransitionWorkThread { // Made this method on package private class public [for jdk1.2 security] public PrefetchWorkThread(BasicController mc) { controller = mc; setName(getName() + ": " + mc); } @Override protected void aborted() { controller.abortPrefetch(); } @Override protected void completed() { controller.completePrefetch(); } @Override protected void failed() { controller.doFailedPrefetch(); } @Override protected boolean process() { return controller.doPrefetch(); } } /** * A Thread to take care of realizing and catching of RealizeCompleteEvents from * down stream nodes. */ class RealizeWorkThread extends StateTransitionWorkThread { // Made this method on package private class public [for jdk1.2 security] public RealizeWorkThread(BasicController mc) { controller = mc; setName(getName() + ": " + mc); } @Override protected void aborted() { controller.abortRealize(); } @Override protected void completed() { controller.completeRealize(); } @Override protected void failed() { controller.doFailedRealize(); } @Override protected boolean process() { return controller.doRealize(); } } /** * The event queue to post outgoing events for the BasicController. The queue is * running is a separate thread. This is so the posting of the events won't * block the controller. */ class SendEventQueue extends ThreadedEventQueue { private BasicController controller; public SendEventQueue(BasicController c) { controller = c; } /** * Callback from the thread when there is an event to be processed. In this * case, we call controller's dispatchEvent() to send the event to the * listening controllers. */ @Override public void processEvent(ControllerEvent evt) { controller.dispatchEvent(evt); } } /** * An execution thread to take care of processing and waiting of completion * events for BasicController. Subclass this to build PrefetchWork thread for * example. */ abstract class StateTransitionWorkThread extends MediaThread { BasicController controller; boolean allEventsArrived = false; StateTransitionWorkThread() { useControlPriority(); } /** * Called if the processing is aborted in the middle. */ protected abstract void aborted(); /** * This will be invoked when everything is ready. i.e., the processing is * completed and all the events from down stream nodes have been fully * captured. */ protected abstract void completed(); /** * Called if the processing failed. */ protected abstract void failed(); /** * Implement this to do the real work. */ protected abstract boolean process(); /** * run() method for the thread. Do the work, wait for every expected events * to arrive, signal the the completion. */ @Override public void run() { controller.resetInterrupt(); try { boolean success = process(); if (controller.isInterrupted()) aborted(); else if (success) completed(); else failed(); } catch (OutOfMemoryError e) { System.err.println("Out of memory!"); } controller.resetInterrupt(); } } /** * A thread to schedule the stop time. */ class StopTimeThread extends TimedActionThread { // Made this method on package private class public [for jdk1.2 security] public StopTimeThread(BasicController mc, long nanoseconds) { super(mc, nanoseconds); setName(getName() + ": StopTimeThread"); wakeupTime = getTime() + nanoseconds; } @Override protected void action() { controller.stopAtTime(); } @Override protected long getTime() { return controller.getMediaNanoseconds(); } } /** * Start a countdown thread to perform an action at the specified time. */ abstract class TimedActionThread extends MediaThread { protected BasicController controller; protected long wakeupTime; protected boolean aborted = false; TimedActionThread(BasicController mc, long nanoseconds) { controller = mc; useControlPriority(); wakeupTime = nanoseconds; } public synchronized void abort() { aborted = true; notify(); } protected abstract void action(); protected abstract long getTime(); @Override public void run() { long sleepTime, now; while (true) { now = getTime(); if (now >= wakeupTime || aborted) break; // Don't sleep more than 1 sec. sleepTime = wakeupTime - now; if (sleepTime > 1000000000L) sleepTime = 1000000000L; synchronized (this) { try { wait(sleepTime / 1000000); } catch (InterruptedException e) { break; } } } if (!aborted) action(); } } /** * A Thread to schedule the start of the node. */ class TimedStartThread extends TimedActionThread { // Made this method on package private class public [for jdk1.2 security] public TimedStartThread(BasicController mc, long tbt) { super(mc, tbt); setName(getName() + ": TimedStartThread"); } @Override protected void action() { controller.doStart(); } @Override protected long getTime() { return controller.getTimeBase().getNanoseconds(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy