Sun Java Solaris Communities My SDN Account Join SDN

1.0 Programmers Guide

1.0 Overview

 
TOC  Prev  Next  

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:

  • TimeLineController.java

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.

  • TimeLineEvent.java

An event posted by the TimeLineController when the segment in the time line changes.

  • EventPostingBase.java

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.

  • ListenerList.java

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.

  • EventPoster

A class that spins a thread to post events to a ControllerListener.

  • BasicClock

A simple Clock implementation that implements all of the Clock methods.

TimeLineController.java


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


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.java


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.java


// A list of controller listeners that we are supposed to send // events to. class ListenerList { ControllerListener observer; ListenerList next; }

EventPoster.java


class EventPoster { void postEvent(Object object, ControllerEvent evt) { // Post event. } }

TOC  Prev  Next