FileDocCategorySizeDatePackage
PlaybackEngine.javaAPI DocJMF 2.1.1e52655Mon May 12 12:20:50 BST 2003com.sun.media

PlaybackEngine

public class PlaybackEngine extends BasicController implements ModuleListener
PlaybackEngine implements the media engine for playback.

Fields Summary
protected BasicPlayer
player
protected DataSource
dsource
protected Vector
modules
protected Vector
filters
protected Vector
sinks
protected Vector
waitPrefetched
protected Vector
waitStopped
protected Vector
waitEnded
protected Vector
waitResetted
protected Track[]
tracks
protected Demultiplexer
parser
protected BasicSinkModule
masterSink
protected BasicSourceModule
source
protected SlaveClock
slaveClock
private boolean
internalErrorOccurred
protected boolean
prefetched
protected boolean
started
private boolean
dataPathBlocked
private boolean
useMoreRenderBuffer
private boolean
deallocated
public boolean
prefetchEnabled
protected static boolean
needSavingDB
private Time
timeBeforeAbortPrefetch
private float
rate
protected BitRateControl
bitRateControl
protected FrameRateControl
frameRateControl
protected FramePositioningControl
framePositioningControl
private long
latency
static boolean
DEBUG
protected JMD
jmd
protected Container
container
public static boolean
TRACE_ON
protected BasicTrackControl[]
trackControls
protected ProgressControl
progressControl
private long
realizeTime
private long
prefetchTime
static String
NOT_CONFIGURED_ERROR
static String
NOT_REALIZED_ERROR
static String
STARTED_ERROR
String
configError
String
configIntError
String
configInt2Error
String
parseError
protected String
realizeError
protected String
timeBaseError
protected String
genericProcessorError
String
prefetchError
RTPInfo
rtpInfo
boolean
testedRTP
boolean
prefetchLogged
long
markedDataStartTime
boolean
reportOnce
long
lastBitRate
long
lastStatsTime
static boolean
USE_MASTER
static boolean
USE_BACKUP
Constructors Summary
public PlaybackEngine(BasicPlayer p)

	long initTime = System.currentTimeMillis();

	player = p;
	createProgressControl();

	setClock(slaveClock = new SlaveClock());
	stopThreadEnabled = false;

	profile("instantiation", initTime);
    
Methods Summary
protected synchronized voidabortConfigure()
Called when doConfigure() is aborted.

	// The parser could be in the middle of realizing.  We'll need to
	// abort it.
	if (source != null)
	    source.abortRealize();
    
protected synchronized voidabortPrefetch()
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.

	// Mark the time before the prefetch.  At the next prefetch,
	// we'll sync up the media with this time.
	timeBeforeAbortPrefetch = getMediaTime();
	doReset();
	StateTransistor m;
	int size = modules.size();
	for (int i = 0; i < size; i++) {
	    m = (StateTransistor)modules.elementAt(i);
	    m.abortPrefetch();
	}
	deallocated = true;
    
protected synchronized voidabortRealize()
Called when the realize() is aborted, i.e. deallocate() was called while realizing. Release all resources claimed previously by the realize() call.

	StateTransistor m;
	int size = modules.size();
	for (int i = 0; i < size; i++) {
	    m = (StateTransistor)modules.elementAt(i);
	    m.abortRealize();
	}
    
public booleanaudioEnabled()
Return true if audio is present.

	for (int i = 0; i < trackControls.length; i++) {
	    if (trackControls[i].isEnabled() &&
		trackControls[i].getOriginalFormat() instanceof AudioFormat)
		return true;
	}
	return false;
    
public voidbufferPrefetched(com.sun.media.Module src)


        
	if (!prefetchEnabled)
	    return;
	if (src instanceof BasicSinkModule) {
	    synchronized (waitPrefetched) {
		if (waitPrefetched.contains(src))
		    waitPrefetched.removeElement(src);
		if (waitPrefetched.isEmpty()) {
		    // All sinks are prefetched.  Wake up the
		    // doPrefetch() wait.
		    waitPrefetched.notifyAll();

		    if (!prefetchLogged) {
			profile("prefetch", prefetchTime);
			prefetchLogged = true;
		    }

		    if (getState() != Controller.Started &&
			getTargetState() != Controller.Started) {
			// Non-blocking stop.
			source.pause();	
		    }
		    prefetched = true;
		}
	    }
	}
    
protected com.sun.media.GraphNodebuildTrackFromGraph(com.sun.media.BasicTrackControl tc, com.sun.media.GraphNode node)
Construct a track (connected modules) from the specified node graph. Return a non-null GraphNode if the track cannot be built. The non-null node returned is the node that failed to be opened.

	BasicModule src = null, dst = null;
	InputConnector ic = null;
	OutputConnector oc = null;
	boolean lastNode = true;
	Vector used = new Vector(5);
	int indent = 0;

	if (node.plugin == null) {
	    // There's nothing to build.
	    // i.e. the output from the source (demux) works just fine.
	    // Probably just need to be multiplexed.
	    return null;
	}

	Log.setIndent(indent++);

	// Build the graph from the last node.
	while (node != null && node.plugin != null) {

	    if ((src = createModule(node, used)) == null) {
		// something terrible went wrong.
		Log.error("Internal error: buildTrackFromGraph");
		node.failed = true;
		return node;
	    }

	    // Mark the renderer module or the last output connector
	    // from in the trackcontrol.
	    if (lastNode) {
		if (src instanceof BasicRendererModule) {

		    tc.rendererModule = (BasicRendererModule)src;

		    // If we are dealing with an audio renderer here, we'll
		    // set the jitter buffer size.
		    if (useMoreRenderBuffer &&
			tc.rendererModule.getRenderer() instanceof AudioRenderer)
			setRenderBufferSize(tc.rendererModule.getRenderer());

		} else if (src instanceof BasicFilterModule) {
		    tc.lastOC = src.getOutputConnector(null);
		    tc.lastOC.setFormat(node.output);
		}
		lastNode = false;
	    }

	    ic = src.getInputConnector(null);
	    ic.setFormat(node.input);

	    if (dst != null) {
		oc = src.getOutputConnector(null);
		ic = dst.getInputConnector(null);
		oc.setFormat(ic.getFormat());
	    }

	    src.setController(this);
	    if (!src.doRealize()) {
		//Log.write("Failed to open plugin: " + node.plugin);
		//Log.write("  with input: " + node.input);
		Log.setIndent(indent--);
		node.failed = true;
		return node;
	    }

	    if (oc != null && ic != null)
		connectModules(oc, ic, dst);

	    dst = src;
	    node = node.prev;
	}

	// All successful, so we need to add each modules under the engine's
	// management.

	dst = src;
	while (true) {
	    dst.setModuleListener(this);
	    modules.addElement(dst);
	    tc.modules.addElement(dst);
	    if (dst instanceof BasicFilterModule)
		filters.addElement(dst);
	    else if (dst instanceof BasicSinkModule)
		sinks.addElement(dst);
	    oc = dst.getOutputConnector(null);
	    if (oc == null ||
		(ic = oc.getInputConnector()) == null ||
		(dst = (BasicModule)ic.getModule()) == null)
		break;
	}

	// Set the first output connector.
	tc.firstOC.setFormat(tc.getOriginalFormat());

	// Check if the first module has the format set.
	ic = src.getInputConnector(null);
	Format fmt = ic.getFormat();
	if (fmt == null || !fmt.equals(tc.getOriginalFormat()))
	    ic.setFormat(tc.getOriginalFormat());

	connectModules(tc.firstOC, ic, src);

	Log.setIndent(indent--);

	return null;
    
protected voidconnectModules(com.sun.media.OutputConnector oc, com.sun.media.InputConnector ic, com.sun.media.BasicModule dst)
Connect the two given modules.

	// If the dst is the renderer, adopt the dest's protocol.
	// Otherwise, adopt the source's protocol.
	if (dst instanceof BasicRendererModule)
	    oc.setProtocol(ic.getProtocol());
	else
	    ic.setProtocol(oc.getProtocol());

	oc.connectTo(ic, ic.getFormat());
    
protected com.sun.media.BasicModulecreateModule(com.sun.media.GraphNode n, java.util.Vector used)
Create a realized filter module given the plugIn codec.

	PlugIn p;
	BasicModule m = null;

	if (n.plugin == null)
	    return null;

	if (used.contains(n.plugin)) {
	    // That plugin has already been used in the same path,
	    // we'll need to instantiate another one of its kind.
	    if (n.cname == null || 
		(p = SimpleGraphBuilder.createPlugIn(n.cname, -1)) == null) {
		Log.write("Failed to instantiate " + n.cname);
		return null;
	    }
	} else {
	    p = n.plugin;
	    used.addElement(p);
	}

	if ((n.type == -1 || n.type == PlugInManager.RENDERER) && 
	    p instanceof Renderer) {
	    m = new BasicRendererModule((Renderer)p);
	} else if ((n.type == -1 || n.type == PlugInManager.CODEC) && 
		   p instanceof Codec) {
	    m = new BasicFilterModule((Codec)p);
	}

	if (DEBUG && m != null) m.setJMD(jmd);

	return m;
    
public voidcreateProgressControl()
Create the progress status control.

	// Build the progress control.
	StringControl frameRate;
	StringControl bitRate;
	StringControl videoProps;
	StringControl audioProps;
	StringControl videoCodec;
	StringControl audioCodec;

        frameRate = new StringControlAdapter();
        frameRate.setValue(JMFI18N.getResource("mediaplayer.N/A"));
        bitRate = new StringControlAdapter();
        bitRate.setValue(JMFI18N.getResource("mediaplayer.N/A"));
        videoProps = new StringControlAdapter();
        videoProps.setValue(JMFI18N.getResource("mediaplayer.N/A"));
        audioProps = new StringControlAdapter();
        audioProps.setValue(JMFI18N.getResource("mediaplayer.N/A"));
        audioCodec = new StringControlAdapter();
        audioCodec.setValue(JMFI18N.getResource("mediaplayer.N/A"));
        videoCodec = new StringControlAdapter();
        videoCodec.setValue(JMFI18N.getResource("mediaplayer.N/A"));
        progressControl = new ProgressControlAdapter( frameRate, 
						      bitRate, 
						      videoProps,
						      audioProps,
						      videoCodec,
						      audioCodec );
    
protected java.awt.ComponentcreateVisualContainer(java.util.Vector visuals)

	Boolean hint = (Boolean) Manager.getHint(Manager.LIGHTWEIGHT_RENDERER);
	if (container == null) {
	    if (hint == null || hint.booleanValue() == false) {
		container = new HeavyPanel(visuals);
	    } else {
		container = new LightPanel(visuals);
	    }
	
	    container.setLayout( new FlowLayout() );
	    container.setBackground(Color.black);
	    for (int i = 0; i < visuals.size(); i++) {
		Component c = (Component)visuals.elementAt(i);
		container.add(c);
		c.setSize(c.getPreferredSize());
	    }
	}
	return container;
    
public voiddataBlocked(com.sun.media.Module src, boolean blocked)


	dataPathBlocked = blocked;
  	if (blocked) {
	    // Break the lock for the blocking prefetch & reset.
	    resetPrefetchedList();
	    resetResettedList();
	}

	if ( getTargetState() != Controller.Started) {
	    return;
	}

  	if (blocked) {
	    localStop();
	    setTargetState(Controller.Started); // NOTE: may cause race condition
	    sendEvent( new RestartingEvent(this,
					Controller.Started,
					Controller.Prefetching,
					Controller.Started,
					getMediaTime()));
	} else {
	    sendEvent( new StartEvent(this,
				       Controller.Prefetched,
				       Controller.Started,
				       Controller.Started,
				       getMediaTime(),
				       getTimeBase().getTime()));
	}
    
protected synchronized voiddoClose()
Invoked by close() to cleanup the Controller. Override this to implement subclass behavior.

	if (modules == null) {
	    if (source != null) 
		source.doClose();
	    return;
	}
	if (getState() == Started)
	    localStop();
	if (getState() == Prefetched)
	    doReset();
	StateTransistor m;
	int size = modules.size();
	for (int i = 0; i < size; i++) {
	    m = (StateTransistor)modules.elementAt(i);
	    m.doClose();
	}

	if (needSavingDB) {
	    Resource.saveDB();
	    needSavingDB = false;
	}
    
protected booleandoConfigure()
Configuring the engine.


            
       

	if (!doConfigure1())
	    return false;

	// The indices to the connector names, tracks, and track controls
	// should all correspond to each other.
	String names[] = source.getOutputConnectorNames();
	trackControls = new BasicTrackControl[tracks.length];
	for (int i = 0; i < tracks.length; i++) {
	    trackControls[i] = new PlayerTControl(this, tracks[i], 
				source.getOutputConnector(names[i]));
	}

	return doConfigure2();
    
protected booleandoConfigure1()
Configure - Part I.


	long parsingTime = System.currentTimeMillis();

	modules = new Vector();
        filters = new Vector();
	sinks = new Vector();
	waitPrefetched = new Vector();
	waitStopped = new Vector();
	waitEnded = new Vector();
	waitResetted = new Vector();

	source.setModuleListener(this);
	source.setController(this);
	modules.addElement(source);

	// Realize the source.
	if (!source.doRealize()) {
	    Log.error(configError);
	    if (source.errMsg != null)
		Log.error("  " + source.errMsg + "\n");
	    player.processError = parseError;
	    return false;
	}

	// Check for interrupt after a critical section.
 	if (isInterrupted()) {
	    Log.error(configError);
	    Log.error(configIntError);
	    player.processError = configInt2Error;
	    return false;
	}

	if ((parser = source.getDemultiplexer()) == null) {
	    Log.error(configError);
	    Log.error("  Cannot obtain demultiplexer for the source.\n");
	    player.processError = parseError;
	    return false;
	}

	// Build the track controls.
	try {
	    tracks = parser.getTracks();
	} catch (Exception e) {
	    Log.error(configError);
	    Log.error("  Cannot obtain tracks from the demultiplexer: " + e + "\n");
	    player.processError = parseError;
	    return false;
	}

	// Check for interrupt after a critical section.
 	if (isInterrupted()) {
	    Log.error(configError);
	    Log.error(configIntError);
	    player.processError = configInt2Error;
	    return false;
	}

	profile("parsing", parsingTime);

	return true;
    
protected booleandoConfigure2()
Configure - Part II.


	if (parser.isPositionable() && parser.isRandomAccess()) {
	    Track master = FramePositioningAdapter.getMasterTrack(tracks);
	    if (master != null) {
		framePositioningControl = new FramePositioningAdapter(player, master);
	    }
	}

	return true;
    
protected voiddoDeallocate()
Called by deallocate(). Subclasses should implement this for its specific behavior.

    
protected synchronized voiddoFailedPrefetch()
Called when the prefetch() has failed.

	StateTransistor m;
	int size = modules.size();
	for (int i = 0; i < size; i++) {
	    m = (StateTransistor)modules.elementAt(i);
	    m.doFailedPrefetch();
	}
	super.doFailedPrefetch();
    
protected synchronized voiddoFailedRealize()
Called when realize() has failed.

	StateTransistor m;
	int size = modules.size();
	for (int i = 0; i < size; i++) {
	    m = (StateTransistor)modules.elementAt(i);
	    m.doFailedRealize();
	}
	super.doFailedRealize();
    
protected synchronized booleandoPrefetch()
The stub function to perform the steps to prefetch the controller.

return
true if successful.


                         
        

	if (prefetched)
	    return true;

	return doPrefetch1() && doPrefetch2();
    
protected booleandoPrefetch1()
doPrefetch - Part I


	// If the engine was deallocated before this prefetch, we'll
	// need to sync up the media at the time when the prefetch
	// happens.
	if (timeBeforeAbortPrefetch != null) {
	    doSetMediaTime(timeBeforeAbortPrefetch);
	    timeBeforeAbortPrefetch = null;
	}

	prefetchTime = System.currentTimeMillis();

	// Then prefetch the data path by starting the source 
	// module without starting the renderer modules. 
	resetPrefetchedList();

	// First prefetch each modules.

	// Fail if source cannot be prefetched.
	if (!source.doPrefetch()) {
	    Log.error(prefetchError);
	    if (dsource != null)
		Log.error("  Cannot prefetch the source: " + dsource.getLocator() + "\n"); 
	    return false;
	}

	// Prefetch each track.
	boolean atLeastOneTrack = false;
	boolean usedToFailed;
	for (int i = 0; i < trackControls.length; i++) {

	    usedToFailed = trackControls[i].prefetchFailed;

	    // If this track used to fail and we haven't been
	    // deallocated, then we just skip the track.
	    if (usedToFailed && getState() > Prefetching)
		continue;

	    if (trackControls[i].prefetchTrack()) {
		atLeastOneTrack = true;
		if (usedToFailed) {
		    if (!manageTimeBases()) {
			Log.error(prefetchError);
			Log.error(timeBaseError);
			return false;
		    }
		    // Sync up all the tracks with the current media time
		    // since a new track is added.
		    doSetMediaTime(getMediaTime());
		}

	    } else {

		trackControls[i].prError();

		// If the failed track contain the time base,
		// we'll need to choose a new time base.
		if (trackControls[i].isTimeBase()) {

		    // Fails if no new time base can be found.
		    // This should be rare.
		    if (!manageTimeBases()) {
			Log.error(prefetchError);
			Log.error(timeBaseError);
			player.processError = timeBaseError;
			return false;
		    }
		}

		if (trackControls[i].getFormat() instanceof AudioFormat &&
		    trackControls[i].rendererFailed) {
		    player.processError = "cannot open the audio device.";
		}
	    }
	}

	// Fail if not even one track can be prefetched.
	if (!atLeastOneTrack) {
	    Log.error(prefetchError);
	    return false;
	}

	player.processError = null;

	return true;
    
protected booleandoPrefetch2()
doPrefetch - Part II


	if (prefetchEnabled) {

	    // Wait for notification from the renderer modules with the 
	    // bufferPrefetched event.
	    synchronized (waitPrefetched) {
		source.doStart();
		try {
		    if (!waitPrefetched.isEmpty()) {
			// Block for at most 3 sec. in case anything goes wrong
			// at the renderer modules.
			waitPrefetched.wait(3000);
		    }
		} catch (InterruptedException e) {}
	    }

	} else
	    prefetched = true;

	deallocated = false;

	return true;
    
protected synchronized booleandoRealize()

return
true if successful.


              
        
	return doRealize1() && doRealize2();
    
protected booleandoRealize1()
doRealize Part I


	Log.comment("Building flow graph for: " + dsource.getLocator() + "\n");

	realizeTime = System.currentTimeMillis();

	boolean atLeastOneTrack = false;
	int trackID = 0;
	int numTracks = getNumTracks();

	// Go thru each track to build the graph.
	for (int i = 0; i < trackControls.length; i++) {
	    // Skip the track if its disabled
	    if (!trackControls[i].isEnabled())
		continue;
	    
	    Log.setIndent(0);
	    Log.comment("Building Track: " + i);

	    if (trackControls[i].buildTrack(trackID, numTracks)) {
	        // We've completed at least one track now.
	        atLeastOneTrack = true;
		trackControls[i].setEnabled(true);
	    } else if (trackControls[i].isCustomized()) {
		// If the track is being customized and we cannot
		// build a graph for that track, we won't attempt to
		// build the rest of the tracks.
		Log.error(realizeError);
		trackControls[i].prError();
		player.processError = genericProcessorError;
		return false;
	    } else {
		// Disable that track
		trackControls[i].setEnabled(false);
		Log.warning("Failed to handle track " + i);
		trackControls[i].prError();
	    }

	    // Check for interrupt after a critcial section.
	    if (isInterrupted()) {
		Log.error(realizeError);
		Log.error("  The graph building process is being interrupted.\n");
		player.processError = "interrupted while the player is being constructed.";
		return false;
	    }

	    trackID++;

	    Log.write("\n");
	}

	// Failed if none of the tracks worked. 
	if (!atLeastOneTrack) {
	    Log.error(realizeError);
	    player.processError = "input media not supported: " + getCodecList();
	    return false;
	}

	return true;
    
protected booleandoRealize2()
doRealize Part II


	// Locate the set the master time base.
	if (!manageTimeBases()) {
	    Log.error(realizeError);
	    Log.error(timeBaseError);
	    player.processError = timeBaseError;
	    return false;
	}

	// For debugging
	Log.comment("Here's the completed flow graph:");
	traceGraph(source);
	Log.write("\n");

	profile("graph building", realizeTime);
	realizeTime = System.currentTimeMillis();

	// Update the format info on the progress control.
	updateFormats();

	// For Java Media Debugger
	if (DEBUG) jmd.initGraph(source);

	profile("realize, post graph building", realizeTime);

	return true;
    
protected synchronized voiddoReset()
The real reset code. This is a blocking call and should only be called when the engine is stopped.


	// We'll need to block until the modules are fully resetted to
	// move on.
	synchronized (waitResetted) {

	    resetResettedList();

	    // Put all modules in the reset state.  The source module
	    // is the last to receive the reset call.  It will trigger
	    // zero-length flush buffers to flow from the source to
	    // the sinks.  When the sinks receive the flush buffers,
	    // it will reply back with the resetted callbacks.

	    BasicModule m;
	    int size = modules.size();
	    for (int i = size-1; i >= 0; i--) {
		m = (BasicModule)modules.elementAt(i);
		if (!m.prefetchFailed())
		    m.reset();
	    }

	    // The data flow (including the flush buffers) could be blocked 
	    // at the sinks.  To initiate the reset, we'll call 
	    // triggerReset on the sinks.
	    BasicSinkModule bsm;
	    size = sinks.size();
	    for (int i = 0; i < size; i++) {
		bsm = (BasicSinkModule)sinks.elementAt(i);
		if (!bsm.prefetchFailed())
		    bsm.triggerReset();
	    }

	    if (!waitResetted.isEmpty()) {
		try {
		    // Wait at most 3 seconds in case anything goes
		    // wrong at the plugins.  We have no control on
		    // how plugin writers write their plugin's.
		    waitResetted.wait(3000);
		} catch (Exception e) {}
	    }

	    // Some of the sinks may need to be stop again.
	    size = sinks.size();
	    for (int i = 0; i < size; i++) {
		bsm = (BasicSinkModule)sinks.elementAt(i);
		if (!bsm.prefetchFailed())
		    bsm.doneReset();
	    }
	}

	prefetched = false;
    
protected voiddoSetMediaTime(javax.media.Time when)


	slaveClock.setMediaTime(when);

	// Set media time on the renderer modules and the source.
	// Check the return time from the parser.
	Time t;
	if ((t = source.setPosition(when, 0)) == null)
	    t = when;

	int size = sinks.size();
	BasicSinkModule bsm;
	for (int i = 0; i < size; i++) {
	    bsm = (BasicSinkModule)sinks.elementAt(i);
	    bsm.doSetMediaTime(when);
	    bsm.setPreroll(when.getNanoseconds(), t.getNanoseconds());
	}
    
public synchronized floatdoSetRate(float r)


	if (r <= 0f)
	    r = 1.0f;

	if (r == rate)
	    return r;

	// If the system clock is in use, we need to set rate
	// on the system clock.  Otherwise, set rate on the
	// master clock and let it determine the actual rate use.
	if (masterSink == null)
	    r = getClock().setRate(r);
	else
	    r = masterSink.doSetRate(r);

	BasicModule m;
	int size = modules.size();
	for (int i = 0; i < size; i++) {
	    m = (BasicModule)modules.elementAt(i);
	    if (m != masterSink)
		m.doSetRate(r);
	}

	rate = r;

	return r;
    
protected synchronized voiddoStart()
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.


	if (started) return;

	doStart1();
	doStart2();
    
protected voiddoStart1()
doStart - Part I.


	// If the data source is a capture device, we would like
	// to flush the data source before starting. 
	if (dsource instanceof CaptureDevice || isRTP())
	    reset();

	resetPrefetchedList();
	resetStoppedList();
	resetEndedList();

	for (int i = 0; i < trackControls.length; i++) {
	    if (trackControls[i].isEnabled())
		trackControls[i].startTrack();
	}
    
protected voiddoStart2()
doStart - Part II

	source.doStart();
	started = true;
	prefetched = true;
    
protected synchronized voiddoStop()
Invoked from stop(). Override this to implement subclass behavior.

	if (!started) return;

	doStop1();
	doStop2();
    
protected voiddoStop1()
doStop - Part I.


	resetPrefetchedList();

	source.doStop();

	for (int i = 0; i < trackControls.length; i++) {
	    if (trackControls[i].isEnabled())
		trackControls[i].stopTrack();
	}
    
protected voiddoStop2()
doStop - Part II.


	// If prefetching is disabled, we'll also need to do a
	// non-blocking stop on the source since stop is normally
	// called when data is prefetched.
	if (!prefetchEnabled)
	    source.pause();

	started = false;
    
protected com.sun.media.BasicSinkModulefindMasterSink()
Returns the DataSink which holds the timebase.

	// Search for all the enabled track for a master time base.

	for (int i = 0; i < trackControls.length; i++) {

	    if (!trackControls[i].isEnabled())
		continue;

	    if (trackControls[i].rendererModule != null &&
		trackControls[i].rendererModule.getClock() != null) {
		return trackControls[i].rendererModule;
	    }
	}

	return null;
    
public voidformatChanged(com.sun.media.Module src, javax.media.Format oldFormat, javax.media.Format newFormat)

	Log.comment(src + ": input format changed: " + newFormat);

	// Handle size change independently.
	if (src instanceof BasicRendererModule && 
	    oldFormat instanceof VideoFormat && 
	    newFormat instanceof VideoFormat) {
	    Dimension s1 = ((VideoFormat)oldFormat).getSize();
	    Dimension s2 = ((VideoFormat)newFormat).getSize();
	    if (s2 != null && (s1 == null || !s1.equals(s2))) {
		sendEvent(new SizeChangeEvent(this, s2.width, s2.height, 1.0f));
	    }
	}
    
public voidformatChangedFailure(com.sun.media.Module src, javax.media.Format oldFormat, javax.media.Format newFormat)

	// Send an internal error event for now.
	// Future: need to rebuild the graph.
	// -ivg
	if (!internalErrorOccurred) {
	    sendEvent(new InternalErrorEvent(this, "Internal module " + src + ": failed to handle a data format change!")); 
	    internalErrorOccurred = true;
	    close();
	}
    
public voidframesBehind(com.sun.media.Module src, float frames, com.sun.media.InputConnector ic)


	OutputConnector oc;
	BasicFilterModule bfm;

	while (ic != null) {
	    if ((oc = ic.getOutputConnector()) == null)
		break;
	    if ((src = oc.getModule()) == null)
		break;
	    if (!(src instanceof BasicFilterModule))
		break;
	    bfm = (BasicFilterModule)src;
	    bfm.setFramesBehind(frames);
	    ic = src.getInputConnector(null);
	}
    
protected longgetBitRate()

	return source.getBitsRead();
    
public java.lang.StringgetCNAME()


	if (rtpInfo == null) {
	    if ((rtpInfo = (RTPInfo)dsource.getControl("com.sun.media.util.RTPInfo")) == null)
		return null;
	}

	return rtpInfo.getCNAME();
    
java.lang.StringgetCodecList()

	String list = "";
	Format fmt;
	for (int i = 0; i < trackControls.length; i++) {
	    fmt = trackControls[i].getOriginalFormat();
	    if (fmt == null || fmt.getEncoding() == null)
		continue;

	    list += fmt.getEncoding();
	    if (fmt instanceof VideoFormat)
		list += " video";
	    else if (fmt instanceof AudioFormat)
		list += " audio";
	    if (i + 1 < trackControls.length)
		list += ", ";
	}
	return list;
    
public javax.media.Control[]getControls()
Return a list of Control objects this Controller supports. In this case, it is all the controls from all the modules controlled by this engine.

return
list of Controller controls.


	// build the list of controls.  It is the total of all the
	// controls from each module plus the ones that are maintained
	// by the engine itself (e.g. progressControl).

	Control controls[];
	Vector cv = new Vector();
	Control c;
	Object cs[];
	Module m;
	int i, size = (modules == null ? 0 : modules.size());
	int otherSize = 0;

	// Collect controls from all the modules.
	for (i = 0; i < size; i++) {
	    m = (Module)modules.elementAt(i);
	    cs = m.getControls();
	    if (cs == null) continue;
	    for (int j = 0; j < cs.length; j++) {
		cv.addElement(cs[j]);
	    }
	}

	size = cv.size();

	// Controls owned by the engine itself.

	if (videoEnabled()) {
	    if (frameRateControl == null) {
		frameRateControl = new FrameRateAdapter(player, 0f, 0f, 30f, false) {
		    
		    public float setFrameRate(float rate) {
			this.value = rate;
			return -1f;
		    }
		    
		    public Component getControlComponent() {
			return null;
		    }

		    public Object getOwner() {
			return player;
		    }
		};
	    }
	}

	if (bitRateControl == null) {
	    bitRateControl = new BitRateA(0, -1, -1, false);
	}	
	if (frameRateControl != null)
	    otherSize++;

	if (bitRateControl != null)
	    otherSize++;

	if (framePositioningControl != null)
	    otherSize++;
	
	if (DEBUG) otherSize++;

	controls = new Control[size + otherSize + trackControls.length];

	for (i = 0; i < size; i++)
	    controls[i] = (Control)cv.elementAt(i);

	if (bitRateControl != null)
	    controls[size++] = bitRateControl;

	if (frameRateControl != null)
	    controls[size++] = frameRateControl;

	if (framePositioningControl != null)
	    controls[size++] = framePositioningControl;

	if (DEBUG)
	    controls[size++] = jmd;
	
	// and the tracks
	for (i = 0; i < trackControls.length; i++) {
	    controls[size + i] = trackControls[i];
	}
	
	return controls;
    
public javax.media.TimegetDuration()
Return the duration of the media. It's unknown until we implement a particular node.

return
the duration of the media.

	return source.getDuration();
    
public javax.media.GainControlgetGainControl()
Get audio gain control.

	return (GainControl)getControl("javax.media.GainControl");
    
public longgetLatency()
Return the run-time latency. It's the time it takes for a packet to travel from the first module to the last module. The time is computed in nanoseconds.

	return latency;
    
intgetNumTracks()

	int num = 0;
	for (int i = 0; i < trackControls.length; i++) {
	    if (trackControls[i].isEnabled())
		num++;
	}
	return num;
    
protected javax.media.PlugIngetPlugIn(com.sun.media.BasicModule m)
Get the plugin from a module. For debugging.


	if (m instanceof BasicSourceModule)
	    return ((BasicSourceModule)m).getDemultiplexer();

	if (m instanceof BasicFilterModule)
	    return ((BasicFilterModule)m).getCodec();

	if (m instanceof BasicRendererModule)
	    return ((BasicRendererModule)m).getRenderer();

	return null;
    
public javax.media.TimegetStartLatency()
Returns the start latency. Don't know until the particular node is implemented.

return
the start latency.

	if ( (state == Unrealized) || (state == Realizing) )
	    throwError(new NotRealizedError("Cannot get start latency from an unrealized controller"));
	return LATENCY_UNKNOWN;
    
public javax.media.TimeBasegetTimeBase()
Override the parent's method to not check for realized state. There's no need to.

	return getClock().getTimeBase();
    
public java.awt.ComponentgetVisualComponent()
Get the visual component where the video is presented.

	Vector visuals = new Vector(1);
	if (modules == null)
	    return null;
	for (int i = 0; i < modules.size(); i++) {
	    BasicModule bm = (BasicModule) modules.elementAt(i);
	    PlugIn pi = getPlugIn(bm);
	    if (pi instanceof VideoRenderer) {
		Component comp = ((VideoRenderer)pi).getComponent();
		if (comp != null)
		    visuals.addElement(comp);
	    }
	}
	if (visuals.size() == 0)
	    return null;
	else if (visuals.size() == 1)
	    return (Component) visuals.elementAt(0);
	else {
	    // Multiple components, put them into one container
	    return createVisualContainer(visuals);
	}
    
public voidinternalErrorOccurred(com.sun.media.Module src)

	if (!internalErrorOccurred) {
	    sendEvent(new InternalErrorEvent(this, "Internal module " + src + " failed!")); 
	    internalErrorOccurred = true;
	    close();
	}
    
protected booleanisConfigurable()
The PlaybackEngine is configurable.

	return true;
    
public booleanisRTP()


       

	if (testedRTP)
	    return rtpInfo != null;

	rtpInfo = (RTPInfo)dsource.getControl("com.sun.media.util.RTPInfo");
	testedRTP = true;

	return rtpInfo != null;
    
static booleanisRawVideo(javax.media.Format fmt)
Return true if the given format is a raw video format.

	return (fmt instanceof RGBFormat || fmt instanceof YUVFormat);
    
protected com.sun.media.BasicModulelastModule(com.sun.media.BasicModule bm)
Given a chain of FilterModules, return the last one of the chain.

	OutputConnector oc;
	InputConnector ic;

	oc = bm.getOutputConnector(null);
	while (oc != null && (ic = oc.getInputConnector()) != null) {
	    bm = (BasicModule)ic.getModule();
	    oc = bm.getOutputConnector(null);
	}

	return bm;
    
protected synchronized voidlocalStop()

	super.stop();
    
booleanmanageTimeBases()
Search and update the master time base.


	// Obtain a master time base from one of its SinkModules.
	masterSink = findMasterSink();

	return updateMasterTimeBase();

    
public voidmarkedDataArrived(com.sun.media.Module src, javax.media.Buffer buffer)

          
	if (src instanceof BasicSourceModule) {
	    markedDataStartTime = getMediaNanoseconds();
	} else {
	    long t = getMediaNanoseconds() - markedDataStartTime;
	    if (t > 0 && t < 1000000000) {
		if (!reportOnce) {
		    Log.comment("Computed latency for video: " + t/1000000 + " ms\n");
		    reportOnce = true;
		}
		latency = (t + latency)/2;
	    }
	}
    
public voidmediaEnded(com.sun.media.Module src)

	if (src instanceof BasicSinkModule) {
	    synchronized (waitEnded) {
		if (waitEnded.contains(src))
		    waitEnded.removeElement(src);
		if (waitEnded.isEmpty()) {
		    // EOM.
		    started = false;
		    stopControllerOnly();
 		    sendEvent(new EndOfMediaEvent(this, Started, 
					Prefetched, getTargetState(), 
					getMediaTime()));
		    slaveClock.reset(USE_MASTER);
		} else if (src == masterSink) {
		    // If the master sink has finished but the
		    // not all the other tracks are finished, we'll
		    // need to switch the clock to using a backup for
		    // the rest of the media.
	    	    slaveClock.reset(USE_BACKUP);
		}
	    }
	}
    
public voidpluginTerminated(com.sun.media.Module src)

	if (!internalErrorOccurred) {
	    sendEvent(new ControllerClosedEvent(this)); 
	    internalErrorOccurred = true;
	    close();
	}
    
static voidprofile(java.lang.String msg, long time)

	Log.profile("Profile: " + msg + ": " + 
		(System.currentTimeMillis() - time) + " ms\n");
    
protected synchronized voidreset()
Flush (reset) the flow graph. This is a blocking call and should only be called when the engine is stopped.

	// If a module has been blocked, we'll need 
	// to return from the reset.
	if (started || !prefetched || dataPathBlocked)
	    return;

	doReset();
    
protected voidresetBitRate()

	source.resetBitsRead();
    
private voidresetEndedList()
Reset the renderer lists.

	synchronized (waitEnded) {
	    waitEnded.removeAllElements();
	    int size = sinks.size();
	    BasicSinkModule bsm;
	    for (int i = 0; i < size; i++) {
		bsm = (BasicSinkModule)sinks.elementAt(i);
		if (!bsm.prefetchFailed())
		    waitEnded.addElement(bsm);
	    }
	    waitEnded.notifyAll();
	}
    
private voidresetPrefetchedList()
Reset the renderer lists.

	synchronized (waitPrefetched) {
	    waitPrefetched.removeAllElements();
	    int size = sinks.size();
	    BasicSinkModule bsm;
	    for (int i = 0; i < size; i++) {
		bsm = (BasicSinkModule)sinks.elementAt(i);
		if (!bsm.prefetchFailed())
		    waitPrefetched.addElement(bsm);
	    }
	    waitPrefetched.notifyAll();
	}
    
private voidresetResettedList()
Reset the renderer lists.

	// Set up the wait queue for the reset notifications first.
	// We'll wait for reset note from sinks and the source only.
	synchronized (waitResetted) {
	    waitResetted.removeAllElements();
	    int size = sinks.size();
	    BasicSinkModule bsm;
	    for (int i = 0; i < size; i++) {
		bsm = (BasicSinkModule)sinks.elementAt(i);
		if (!bsm.prefetchFailed())
		    waitResetted.addElement(bsm);
	    }
	    waitResetted.notifyAll();
	}
    
private voidresetStoppedList()
Reset the renderer lists.

	synchronized (waitStopped) {
	    waitStopped.removeAllElements();
	    int size = sinks.size();
	    BasicSinkModule bsm;
	    for (int i = 0; i < size; i++) {
		bsm = (BasicSinkModule)sinks.elementAt(i);
		if (!bsm.prefetchFailed())
		    waitStopped.addElement(bsm);
	    }
	    waitStopped.notifyAll();
	}
    
public voidresetted(com.sun.media.Module src)

	synchronized (waitResetted) {
	    if (waitResetted.contains(src)) {
		waitResetted.removeElement(src);
	    }
	    if (waitResetted.isEmpty()) {
		// Everyone modules has been resetted.
		waitResetted.notifyAll();
	    }
	}
    
public synchronized voidsetMediaTime(javax.media.Time when)
Override BasicController's setMediaTime so as not to set the media time on the master clock twice.

	if (state < Realized)
	    throwError(new NotRealizedError("Cannot set media time on a unrealized controller"));

	// Chances of this are really rare.  Nevertheless, we'll guard
	// against it.
	if (when.getNanoseconds() == getMediaNanoseconds())
	    return;

	// Reset everything.
	reset();

	// In case deallocate was called, we need to
	// clear the timeBeforeAbortPrefetch flag since we are
	// setting a new time.
	timeBeforeAbortPrefetch = null;

	doSetMediaTime(when);

	// Prefetch the engine again to display the first frame.
	doPrefetch();

	sendEvent(new MediaTimeSetEvent(this, when));
    
public static voidsetMemoryTrace(boolean on)
Turn on memory trace to assit debugging.



                
         
	TRACE_ON = on;
    
public voidsetProgressControl(com.sun.media.controls.ProgressControl p)

	progressControl = p;
    
protected voidsetRenderBufferSize(javax.media.Renderer r)

	BufferControl bc = (BufferControl)r.getControl("javax.media.control.BufferControl");
	if (bc != null)
	    bc.setBufferLength(2000);
    
public voidsetSource(javax.media.protocol.DataSource ds)
Verifies to see if the engine accepts the given source.


	try {
	    source = BasicSourceModule.createModule(ds);
	} catch (IOException ioe) {
	    Log.warning("Input DataSource: " + ds);
	    Log.warning("  Failed with IO exception: " + ioe.getMessage());
	    throw ioe;
	} catch (IncompatibleSourceException ise) {
	    Log.warning("Input DataSource: " + ds);
	    Log.warning("  is not compatible with the MediaEngine.");
	    Log.warning("  It's likely that the DataSource is required to extend PullDataSource;");
	    Log.warning("  and that its source streams implement the Seekable interface ");
	    Log.warning("  and with random access capability.");
	    throw ise;
	}

	if (source == null)
	    throw new IncompatibleSourceException();

	// Create the debugger control
	if (DEBUG && jmd == null) {
	    String jmdTitle = "PlugIn Viewer";
	    if (ds != null && ds.getLocator() != null) {

		jmdTitle = ds.getLocator().toString();

		// We'll determine here if we want to allocate a
		// render buffer for smoother playback.
		// Doing that will increase the latency, so we'll
		// only do that for a few well-known protocols.
		String protocol = ds.getLocator().getProtocol();
		if (protocol != null) {
		    protocol = protocol.toLowerCase();
		    if (protocol.equals("file") ||
			protocol.startsWith("http") ||
			protocol.equals("ftp")) {
			useMoreRenderBuffer = true;
		    }
		}
	    }

	    jmd = new BasicJMD(jmdTitle);
	}
	
	if (DEBUG) source.setJMD(jmd);
	source.setController(this);
	dsource = ds;

	// If it's RTP data source, we'll disable prefetch and reset.
	if (dsource instanceof com.sun.media.protocol.Streamable &&
	    !((com.sun.media.protocol.Streamable)dsource).isPrefetchable()) {
	    prefetchEnabled = false;
	    dataPathBlocked = true;
	}

	if (dsource instanceof CaptureDevice)
	    prefetchEnabled = false;

    
public voidsetStopTime(javax.media.Time t)
Override BasicController.setStopTime to allow for more accurate stop time set.

	if (getState() < Realized)
	    throwError(new NotRealizedError("Cannot set stop time on an unrealized controller."));

	if ( getStopTime() != null && 
	     getStopTime().getNanoseconds() != t.getNanoseconds() )
	    sendEvent(new StopTimeChangeEvent(this, t));

	if (getState() == Started && 
	    t != Clock.RESET && t.getNanoseconds() < getMediaNanoseconds()) {
	    // We have already passed the stop time.
	    localStop();
	    setStopTime(Clock.RESET);
	    sendEvent(new StopAtTimeEvent(this, getState(),
					       Prefetched,
					       getTargetState(),
					       getMediaTime()));
	} else {
	    getClock().setStopTime(t);

	    int size = sinks.size();
	    BasicSinkModule bsm;
	    for (int i = 0; i < size; i++) {
		bsm = (BasicSinkModule)sinks.elementAt(i);
		bsm.setStopTime(t);
	    }
	}
    
public voidsetTimeBase(javax.media.TimeBase tb)
Override the parent's method to not check for realized state. There's no need to.

	getClock().setTimeBase(tb);
	// Set the time base on each of the SinkModules.
	if (sinks == null)
	    return;
	int size = sinks.size();
	BasicSinkModule bsm;
	for (int i = 0; i < size; i++) {
	    bsm = (BasicSinkModule)sinks.elementAt(i);
	    bsm.setTimeBase(tb);
	}
    
public synchronized voidstop()
This is stop by request.

	super.stop();
	sendEvent(new StopByRequestEvent(this, Started,
				  Prefetched,
				  getTargetState(),
				  getMediaTime()));
    
public voidstopAtTime(com.sun.media.Module src)

	if (src instanceof BasicSinkModule) {
	    synchronized (waitStopped) {
		if (waitStopped.contains(src))
		    waitStopped.removeElement(src);

		// Check to see if all the tracks have stopped or
		// all other tracks have reached EOM and this is the
		// only track left behind.
		if (waitStopped.isEmpty() ||
		    waitEnded.size() == 1 && waitEnded.contains(src)) {
		    // Stop at time.
		    started = false;
		    stopControllerOnly();
		    setStopTime(Clock.RESET);
		    sendEvent(new StopAtTimeEvent(this, Started,
					       Prefetched,
					       getTargetState(),
					       getMediaTime()));
		    slaveClock.reset(USE_MASTER);
		} else if (src == masterSink) {
		    // If the master sink has finished but the
		    // not all the other tracks are finished, we'll
		    // need to switch the clock to using a backup for
		    // the rest of the media.
	    	    slaveClock.reset(USE_BACKUP);
		}
	    }
	}
    
voidtraceGraph(com.sun.media.BasicModule source)
Trace the flow graph for debugging.

	Module m;
	OutputConnector oc;
	InputConnector ic;
	String names[];

	names = source.getOutputConnectorNames();
	for (int i = 0; i < names.length; i++) {

	    oc = source.getOutputConnector(names[i]);
	    if ((ic = oc.getInputConnector()) == null)
		continue;
	    if ((m = ic.getModule()) == null)
		continue;
	    Log.write("  " + getPlugIn(source));
	    Log.write("     connects to: " + getPlugIn((BasicModule)m));
	    Log.write("     format: " + oc.getFormat());
	    traceGraph((BasicModule)m);
	}
    
public voidupdateFormats()
Update the format info per track on the progress control.

	// Update formats per track.
	for (int i = 0; i < trackControls.length; i++) {
	    trackControls[i].updateFormat();
	}
    
booleanupdateMasterTimeBase()
Update the master timebase on all the modules.


	BasicSinkModule bsm;
	int size = sinks.size();

	if (masterSink != null)
	    slaveClock.setMaster(masterSink.getClock());
	else
	    // No master sink found in the module, we'll use the
	    // system time base.
	    slaveClock.setMaster(null);

	// Set the master time base to each of the SinkModules.
	for (int i = 0; i < size; i++) {
	    bsm = (BasicSinkModule)sinks.elementAt(i);
	    if (bsm != masterSink && !bsm.prefetchFailed()) {
		try {
		    bsm.setTimeBase(slaveClock.getTimeBase());
		} catch (IncompatibleTimeBaseException e) {
		    return false;
		}
	    }
	}

	return true;
    
public voidupdateRates()
Update the aggregate bit rate and frame rate per track on the progress control.

  // so now - lastStatsTime will not be 0.

                        
       

	if (getState() < Realized)
	    return;

	// Update global bit rate.
	long now = System.currentTimeMillis();
	long rate, avg;

	if (now == lastStatsTime)
	    rate = lastBitRate;
	else
	    rate = (long)((double)getBitRate()*8.0/
				(now - lastStatsTime)*1000.0);
	avg = (lastBitRate + rate)/2;

	if (bitRateControl != null) {
	    bitRateControl.setBitRate((int)avg);
	}
	
	lastBitRate = rate;
	lastStatsTime = now;
	resetBitRate();

	// Update frame rate on a per-track basis.
	for (int i = 0; i < trackControls.length; i++) {
	    trackControls[i].updateRates(now);
	}

	// Compute the instanteous latency.
	source.checkLatency();
    
public booleanvideoEnabled()
Return true if video is present.

	for (int i = 0; i < trackControls.length; i++) {
	    if (trackControls[i].isEnabled() &&
		trackControls[i].getOriginalFormat() instanceof VideoFormat)
		return true;
	}
	return false;