CONTENTS | PREV | NEXT | INDEX JMF 2.0 API Guide



E

Sample Controller Implementation

This sample illustrates how a simple time-line Controller can be implemented in JMF. This sample is provided as a reference for developers who are implementing their own Controllers. Please note that it has not been tested or optimized for production use.

This sample consists of four classes:

The Controller. You give it an array of time values (representing a time line) and it keeps track of which segment in the time line you are in.
An event posted by the TimeLineController when the segment in the time line changes.
A base class used by TimeLineController that handles the Controller methods addControllerListener and removeControllerListener. It also provides a postEvent method that can be used by the subclass to post events.
A class used to maintain a list of ControllerListener objects that the TimeLineController needs to post events to.

This implementation also uses two additional classes whose implementations are not shown here.

A class that spins a thread to post events to a ControllerListener.
A simple Clock implementation that implements all of the Clock methods.

TimeLineController

Example E-1: TimeLineController.java (1 of 11)
 import javax.media.*;
 import com.sun.media.MediaClock;
 
 // This Controller uses two custom classes:
 //    The base class is EventPostingBase.  It has three methods:
 //      public void addControllerListener (ControllerListener 
 //         observer);
 //      public void removeControllerListener (ControllerListener
 //         observer);
 //      protected void postEvent (ControllerEvent event);
 //
 
 //   This Controller posts TimeLineEvents.  TimeLineEvent has 
 //   two methods:
 //      public TimeLineEvent (Controller who, int
 //         segmentEntered);
 //      public final int getSegment ();
 
 public class TimeLineController extends EventPostingBase 
     implements Controller, Runnable 
 {
     Clock ourClock;
     
     // This simple controller really only has two states: 
     // Prefetched and Started.
 
     int ourState;
     long timeLine[];
     int currentSegment = -1;
     long duration;
     Thread myThread;
 
     // Create a TimeLineController giving it a sorted time line.  
     // The TimeLineController will post events indicating when 
     // it has passed to different parts of the time line.
 
     public TimeLineController (long timeLine[]) 
     {
         this.timeLine = timeLine;
         ourClock = new MediaClock ();
         duration = timeLine[timeLine.length-1];
         myThread = null;
         // We always start off ready to go!
         ourState = Controller.Prefetched;
     }
 
     // Binary search for which segment we are now in.  Segment 
     // 0 is considered to start at 0 and end at timeLine[0].  
     // Segment timeLine.length is considered to start at 
     // timeLine[timeLine.length-1] and end at infinity.  At the 
     // points of 0 and timeLine[timeLine.length-1] the
     // Controller will stop (and post an EndOfMedia event).    
 
     int computeSegment (long time) 
     {
         int max = timeLine.length;
         int min = 0;
         
         for (;;) 
         {
             if (min == max) return min;
             int current = min + ((max - min) >> 1);
         
             if (time < timeLine[current]) 
             {
                 max = current;
             } 
 
             else 
             {
                 min = current + 1;
             }
         }
     }
     // These are all simple...
     
     public float setRate (float factor) 
     {
         // We don't support a rate of 0.0.  Not worth the extra math 
         // to handle something the user should do with the stop() 
         // method!
 
         if (factor == 0.0f) 
         {
             factor = 1.0f;
         }
 
         float newRate = ourClock.setRate (factor);
         postEvent (new RateChangeEvent (this, newRate));
         return newRate;
     }
 
     public void setTimeBase (TimeBase master) 
         throws IncompatibleTimeBaseException 
     {
         ourClock.setTimeBase (master);
     }
 
     public Time getStopTime () 
     {
         return ourClock.getStopTime ();
     }
 
     public Time getSyncTime () 
     {
         return ourClock.getSyncTime ();
     }
 
     public Time mapToTimeBase (Time t) throws ClockStoppedException 
     {
         return ourClock.mapToTimeBase (t);
     }
 
     public Time getMediaTime () 
     {
         return ourClock.getMediaTime ();
     }
 
     public TimeBase getTimeBase () 
     {
         return ourClock.getTimeBase ();
     }
     public float getRate () 
     {
         return ourClock.getRate ();
     }
 
     // From Controller
 
     public int getState () 
     {
         return ourState;
     }
     public int getTargetState () 
     {
         return ourState;
     }
 
     public void realize () 
     {
         postEvent (new RealizeCompleteEvent (this, ourState, 
             ourState, ourState));
     }
 
     public void prefetch () 
     {
       postEvent (new PrefetchCompleteEvent (this, ourState, 
             ourState, ourState));
     }
 
     public void deallocate () 
     {
       postEvent (new DeallocateEvent (this, ourState, 
             ourState, ourState, ourClock.getMediaTime ()));
     }
 
     public Time getStartLatency () 
     {
         // We can start immediately, of course!
 
         return new Time(0);
     }
 
     public Control[] getControls () 
     {
         return new Control[0];
     }
     public Time getDuration () 
     {
         return new Time(duration);
     }
 
     // This one takes a little work as we need to compute if we 
     // changed segments.
 
     public void setMediaTime (Time now) 
     {
         ourClock.setMediaTime (now);
         postEvent (new MediaTimeSetEvent (this, now));
         checkSegmentChange (now.getNanoseconds());
     }
 
     // We now need to spin a thread to compute/observe the 
     // passage of time.
 
     public synchronized void syncStart (Time tbTime) 
     {
         long startTime = ourClock.getMediaTime().getNanoseconds();
 
         // We may actually have to stop immediately with an 
         // EndOfMediaEvent. We compute that now.  If we are already   
         // past end of media, then we
         // first post the StartEvent then we post a EndOfMediaEvent
 
         boolean endOfMedia;
         float rate = ourClock.getRate ();
         
         if ((startTime > duration && rate >= 0.0f) || 
             (startTime < 0 && rate <= 0.0f)) 
         {
             endOfMedia = true;
         } 
         
         else 
         {
             endOfMedia = false;
         }
 
         // We face the same possible problem with being past the stop 
         // time.  If so, we stop immediately.
 
         boolean pastStopTime;
         long stopTime = ourClock.getStopTime().getNanoseconds();
         if ((stopTime != Long.MAX_VALUE) && 
             ((startTime >= stopTime && rate >= 0.0f) ||
             (startTime <= stopTime && rate <= 0.0f))) 
         {
             pastStopTime = true;
         } 
 
         else 
         {
             pastStopTime = false;
         }
 
         if (!endOfMedia && !pastStopTime) 
         {
             ourClock.syncStart (tbTime);
             ourState = Controller.Started;
         }
 
         postEvent (new StartEvent (this, Controller.Prefetched, 
             Controller.Started, Controller.Started, 
             new Time(startTime), tbTime));
 
         if (endOfMedia) 
         {
             postEvent (new EndOfMediaEvent (this,
                 Controller.Started,
                 Controller.Prefetched, Controller.Prefetched,
                 new Time(startTime)));
         } 
 
         else if (pastStopTime) 
         {
             postEvent (new StopAtTimeEvent (this, Controller.Started,
                 Controller.Prefetched, Controller.Prefetched,
                 new Time(startTime)));
         } 
 
         else 
         {
             myThread = new Thread (this, "TimeLineController");
         
             // Set thread to appopriate priority...
             myThread.start ();
         }
     }
 
     
 
     // Nothing really special here except that we need to notify 
     // the thread that we may have.
 
     public synchronized void setStopTime (Time stopTime) 
     {
         ourClock.setStopTime (stopTime);
         postEvent (new StopTimeChangeEvent (this, stopTime));
         notifyAll ();
     }
 
     // This one is also pretty easy.  We stop and tell the running
     // thread to exit.
     public synchronized void stop () 
     {
         int previousState = ourState;
         ourClock.stop ();
         ourState = Controller.Prefetched;
         postEvent (new StopByRequestEvent (this, previousState,
             Controller.Prefetched, Controller.Prefetched,
             ourClock.getMediaTime ()));
         notifyAll ();
 
         // Wait for thread to shut down.
 
         while (myThread != null) 
         {
             try 
             {
                 wait ();
             } 
             catch (InterruptedException e) 
             {
                 // NOT REACHED
             }
         }
     }
 
     protected void checkSegmentChange (long timeNow) 
     {
         int segment = computeSegment (timeNow);
         if (segment != currentSegment) 
         {
             currentSegment = segment;
             postEvent (new TimeLineEvent (this, currentSegment));
         }
     }
 
     // Most of the real work goes here.  We have to decide when 
     // to post events like EndOfMediaEvent and StopAtTimeEvent 
     // and TimeLineEvent.
 
     public synchronized void run () 
     {
         long timeToNextSegment = 0;
         long mediaTimeToWait = 0;
 
         float ourRate = 1.0f;
 
         for (;;) 
         {
             // First, have we changed segments?  If so, post an event!
 
             long timeNow = ourClock.getMediaTime ().getNanoseconds ();
             checkSegmentChange (timeNow);
 
             // Second, have we already been stopped?  If so, stop 
             // the thread.
 
             if (ourState == Controller.Prefetched) 
             {
                 myThread = null;
         
                 // If someone is waiting for the thread to die, let them 
                 // know.
 
                 notifyAll ();
                 break;
             }
 
             // Current rate.  Our setRate() method prevents the value 
             // 0 so we don't check for that here.
 
             ourRate = ourClock.getRate ();
 
             // How long in clock time do we need to wait before doing
             // something?
 
             long endOfMediaTime;
 
             // Next, are we past end of media?
 
             if (ourRate > 0.0f) 
             {
                 mediaTimeToWait = duration - timeNow;
                 endOfMediaTime = duration;
             } 
             else 
             {
                 mediaTimeToWait = timeNow;
                 endOfMediaTime = 0;
             }
 
             // If we are at (or past) time to stop due to EndOfMedia, 
             // we do that now!
             if (mediaTimeToWait <= 0) 
             {
                 ourClock.stop ();
                 ourClock.setMediaTime (new Time(endOfMediaTime));
                 ourState = Controller.Prefetched;
                 postEvent (new EndOfMediaEvent (this, Controller.Started,
                     Controller.Prefetched, Controller.Prefetched,
                     new Time(endOfMediaTime)));
                 continue;
             }
 
             // How long until we hit our stop time?
 
             long stopTime = ourClock.getStopTime ().getNanoseconds();
             if (stopTime != Long.MAX_VALUE) 
             {
                 long timeToStop;
                 if (ourRate > 0.0f) 
                 {
                     timeToStop = stopTime - timeNow;
                 } 
                 else 
                 {
                     timeToStop = timeNow - stopTime;
                 }
 
                 // If we are at (or past) time to stop due to the stop 
                 // time, we stop now!
                 if (timeToStop <= 0) 
                 {
                     ourClock.stop ();
                     ourClock.setMediaTime (new Time(stopTime));
                     ourState = Controller.Prefetched;
                     postEvent (new StopAtTimeEvent (this,
                        Controller.Started,
                         Controller.Prefetched, Controller.Prefetched,
                         new Time(stopTime)));
                     continue;
                 } 
                 else if (timeToStop < mediaTimeToWait) 
                 {
                     mediaTimeToWait = timeToStop;
                 }
             }
             // How long until we pass into the next time line segment?
 
             if (ourRate > 0.0f) 
             {
                 timeToNextSegment = timeLine[currentSegment] - timeNow;
             } 
 
             else if (currentSegment == 0) 
             {
                 timeToNextSegment = timeNow;
             } 
 
             else 
             {
                 timeToNextSegment = timeNow - timeLine[currentSegment-1];
             }
         }
 
         if (timeToNextSegment < mediaTimeToWait) 
         {
             mediaTimeToWait = timeToNextSegment;
         }
                
         // Do the ugly math to compute what value to pass to 
         // wait():
 
         long waitTime;
         if (ourRate > 0) 
         {
             waitTime = (long) ((float) mediaTimeToWait / ourRate)  /
                  1000000;
         }
         else 
         {
             waitTime = (long) ((float) mediaTimeToWait / -ourRate) /
                 1000000;
         }
         // Add one because we just rounded down and we don't 
         // really want to waste CPU being woken up early.
 
         waitTime++;
 
         if (waitTime > 0) 
         {
             // Bug in some systems deals poorly with really large 
             // numbers. We will cap our wait() to 1000 seconds 
             // which point we will probably decide to wait again.
             if (waitTime > 1000000) waitTime = 1000000;
             try 
             {
                 wait (waitTime);
             } 
             catch (InterruptedException e) 
             {
             // NOT REACHED
             }
         }
     }
 
     public void close()
     {
 
     }
 
     public Control getControl(String type)
     {
         return null;
     }
 
     public long getMediaNanoseconds()
     {
         return 0;
     } 
 }

 

TimeLineEvent

Example E-2: TimeLineEvent.java  
 import javax.media.*;
 
 // TimeLineEvent is posted by TimeLineController when we have
 // switched segments in the time line.
 
 public class TimeLineEvent extends ControllerEvent 
 {
     protected int segment;
 
     public TimeLineEvent (Controller source, int currentSegment) 
     {
       super (source);
       segment = currentSegment;
     }
 
     public final int getSegment () 
     {
       return segment;
     }
 }

EventPostingBase

Example E-3: EventPostingBase.java (1 of 3)
 import javax.media.*;
 
 // import COM.yourbiz.media.EventPoster;
 
 // The implementation of the EventPoster class is not included as part
 // of this example. EventPoster supports two methods:
 // public EventPoster ();
 // public void postEvent (ControllerListener who, ControllerEvent
 //        what);
 
 public class EventPostingBase 
 {
     protected ListenerList olist;
     protected Object olistLock;
     protected EventPoster eventPoster;
     // We sync around a new object so that we don't mess with
     // the super class synchronization.
     EventPostingBase () 
     {
         olistLock = new Object ();
     }
 
     public void addControllerListener (ControllerListener observer) 
     {
         synchronized (olistLock) 
         {
             if (eventPoster == null) 
             {
                 eventPoster = new EventPoster ();
             }   
         
             ListenerList iter;
             for (iter = olist; iter != null; iter = iter.next) 
             {
                 if (iter.observer == observer) return;
             }
 
             iter = new ListenerList ();
             iter.next = olist;
             iter.observer = observer;
             olist = iter;  
         }   
     }
 
     public void removeControllerListener (ControllerListener  observer) 
     {
         synchronized (olistLock) 
         {
             if (olist == null) 
             {
                 return;
             } 
             else if (olist.observer == observer) 
             {
                 olist = olist.next;
             } 
             else 
             {
                 ListenerList iter;
                 for (iter = olist; iter.next != null; iter = iter.next) 
                 {
                     if (iter.next.observer == observer) 
                     {
                         iter.next = iter.next.next;
                         return;
                     }
                 }
             }
         }
     }
 
 
     protected void postEvent (ControllerEvent event) 
     {
         synchronized (olistLock) 
         {
             ListenerList iter;
             for (iter = olist; iter != null; iter = iter.next) 
             {
                 eventPoster.postEvent (iter.observer, event);
             }
         }
     }
 }

ListenerList

Example E-4: ListenerList.java  
 // A list of controller listeners that we are supposed to send
 // events to.
 
 class ListenerList 
 {
     ControllerListener observer;
     ListenerList next;
 }

EventPoster

Example E-5: EventPoster.java
 class EventPoster 
 {
     void postEvent(Object object, ControllerEvent evt)
    {
    // Post event.
     }
 }
 



CONTENTS | PREV | NEXT | INDEX

Copyright © 1998-1999 Sun Microsystems, Inc. All Rights Reserved.