FileDocCategorySizeDatePackage
Manager.javaAPI DocJMF 2.1.1e79626Mon May 12 12:20:36 BST 2003javax.media

Manager.java

/*
 * @(#)Manager.java	1.76 03/04/30
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package javax.media;

import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.io.IOException;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Enumeration;
import java.util.Vector;
import java.util.Hashtable;
import javax.media.protocol.DataSource;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PushDataSource;
import javax.media.protocol.PullBufferDataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.URLDataSource;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.SourceCloneable;
import javax.media.control.TrackControl;
import javax.media.Format;
import javax.media.format.AudioFormat;
import javax.media.format.VideoFormat;
import javax.media.control.FormatControl;
import javax.media.protocol.CaptureDevice;
import javax.media.rtp.RTPManager;

// For mesages logging.
import com.sun.media.Log;

/**
 * <code>Manager</code> is the access point for obtaining
 * system dependent resources such as <code>Players</code>,
 * <code>DataSources</code>, <code>Processors</code>, <code>DataSinks</code>,
 * the system <code>TimeBase</code>, the cloneable and merging utility
 * <code>DataSources</code>.
 * <p>
 *
 * A <code>DataSource</code> is an object used to 
 * deliver time-based multimedia data that is specific
 * to a delivery protocol.
 * <p>
 * A <code>Player</code> is an object used to
 * control and render multimedia data that
 * is specific to the content type of the data.
 * <p>
 * A <code>Processor</code> is an object similar to a Player which is
 * used to process data and output the processed data.
 * <p>
 * A <code>DataSink</code> is an object that takes a <code>DataSource</code>
 * as input and renders the output to a specified destination.
 * <p>
 * A <code>DataSource</code> provides
 * a <code>Player</code>, <code>Processor</code> or <code>DataSink</code>
 * with media data;
 * a <CODE>Player</CODE>, <code>Processor</code> or <code>DataSink</code> 
 * must have a <CODE>DataSource</CODE>.
 * <code>Manager</code> provides access to a protocol and media independent
 * mechanism for constructing <code>DataSources</code>, <code>Players</code>, 
 * <code>Processors</code> and <code>DataSinks</code>. 
 *
 * <h2>Creating Players and DataSources</h2>
 *
 * <code>Manager</code> will create<code>Players</code> from a
 * <code>URL</code>, a <CODE>MediaLocator</CODE> or a <code>DataSource</code>.
 * Creating a <code>Player</code> requires the following:
 * <ul>
 * <li> Obtain the connected <code>DataSource</code> for the specified
 * protocol
 * <li> Obtain the <code>Player</code> for the content-type
 * specified by the <code>DataSource</code>
 * <li> Attach the <code>DataSource</code> to the <code>Player</code>
 * using the <code>setSource</code> method.
 * </ul>
 *
 * <h3>Finding DataSources by Protocol</h3>
 *
 * A <code>MediaLocator</code> defines a protocol for obtaining
 * content.
 * <code>DataSources</code> are identified by the protocol
 * that they support. <code>Manager</code> uses the protocol
 * name to find <code>DataSource</code> classes.
 * <p>
 * 
 * To find a <code>DataSource</code> using a <code>MediaLocator</code>,
 * <code>Manager</code> constructs a list of class names from the protocol
 * package-prefix list and the protocol name obtained
 * from the <code>MediaLocator</code>.
 * For each class name in the constructed list a new <code>DataSource</code>
 * is instantiated, the <code>MediaLocator</code> is attached,
 * and the <code>DataSource</code> is connected.
 * If no errors have occurred, the procces is considered finished and the
 * connected <code>DataSource</code> is used by
 * <code>Manager</code> in any following operations.
 * If there was an error then the next class name in the list
 * is tried.
 * The exact details of the search algorithm is described in
 * the method documentation below.
 *
 * <h3>Finding Players by Content Type</h3>
 *
 * A <code>Player</code> is a <code>MediaHandler</code>.
 * A <code>MediaHandler</code> is a an object that reads
 * data from a <code>DataSource</code>. There are three types
 * of supported <code>MediaHandler</code>: <code>MediaProxy</code>,
 * <code>Player</code> and <code>Processor</code>.
 * <p>
 *
 * <code>MediaHandlers</code> are identified by the content type that they
 * support. A <code>DataSource</code> identifies the content type
 * of the data it produces with the <code>getContentType</code> method.
 * <code>Manager</code> uses the content type name to
 * find instances of <code>MediaHandler</code>.
 * <p>
 *
 * To find a <code>MediaHandler</code> using a content type name,
 * <code>Manager</code> constructs a list of class names from
 * the content package-prefix list and the content type name.
 * For each class name in the constructed list a new <code>MediaHandler</code>
 * is instantiated, and the <code>DataSource</code> is attached to
 * the <code>MediaHandler</code> using <coded>MediaHandler.setSource</code>.
 * <p>
 *
 * If the <code>MediaHandler</code> is a <code>Player</code> and the
 * <code>setSource</code> was successful the process is finished
 * and the <code>Player</code> is returned.
 * If the <code>setSource</code> failed, another name in the
 * list is tried.
 * <p>
 *
 * If the <code>MediaHandler</code> is a <code>MediaProxy</code>
 * then a new <code>DataSource</code> is obtained from the
 * <code>MediaProxy</code>, a new list is created for the
 * content type the <code>DataSource</code> supports and the
 * whole thing is tried again.
 * <p>
 * 
 * If a valid <code>Player</code> is not found then the whole
 * procedure is repeated with "unknown" substituted
 * for the content-type name. The "unknown" content type is supported
 * by generic <code>Players</code> that are capable of handling
 * a large variety of media types, often in a platform dependent
 * way.
 *<p>
 *
 * The detailed creation algorithm is specified in the methods below.
 *
 * <h3>Creating a Realized Player</h3> 
 * Versions of <code>createRealizedPlayer</code> calls are provided as an 
 * acceleration to create a Player.  The returned player is in the
 * <I>Realized</I> state.  In addition to <code>NoPlayerException</code>
 * and <code>IOException</code>, <code>CannotRealizeException</code> can
 * be thrown if the <code>Manager</code> cannot realize the 
 * <code>Player</code>.
 * <p>
 *
 * <h2>Creating Processors</h2>
 * 
 * <code>Processors</code> are created in the same way as <code>Players</code>
 * as outlined above.  <code>Manager</code> also provides an additional way 
 * to create a Processor via the <code>createRealizedProcessor</code> call.  A 
 * <code>ProcessorModel</code> is used to fully identify the
 * input and output requirements of a <code>Processor</code>.  The
 * <code>createRealizedProcessor</code> call takes a 
 * <code>ProcessorModel</code> as input and create a <code>Processor</code> 
 * that adheres to the given
 * <code>ProcessorModel</code>.  The returned Processor is
 * in the <I>Realized</I> state.  The method is a blocking call.<p>
 * If the <code>Manager</code> fails to find a <code>Processor</code>
 * that fits the <code>ProcessorModel</code>, a 
 * <code>NoProcessorException</code> is thrown.  If there is a problem
 * creating and realizing a <code>Processor</code>, it will throw an 
 * <code>IOException</code> or <code>CannotRealizeException</code> depending 
 * on the circumstances.
 *
 * <h2>Creating DataSinks</h2>
 *
 * <code>DataSinks</code> are created from an input <code>DataSource</code>
 * <code>MediaLocator</code>.  The <code>MediaLocator</code> identifies the
 * protocol and content of the <code>DataSink</code> to be used.  The
 * search for the particular <code>DataSink</code> class to be created
 * is similar to the process of creating a <code>DataSource</code>. 
 * The detail search and creation algorithm is described in the method
 * documentation below.
 *
 * <h2>Player and Processor Threads</h2>
 *
 * <code>Players</code> and <code>Processors</code> process media data 
 * asynchronously from the main program flow.
 * This implies that a <code>Player</code> or <code>Processor</code> must 
 * often manage one or more threads.
 * The threads managed by the <code>Player</code> or <code>Processor</code>
 * are not in the thread group of the application that calls
 * <code>createPlayer</code> or <code>createProcessor</code>.
 *
 * <h2>System Time Base</h2>
 *
 * All <code>Players</code> need a <code>TimeBase</code>. Many  
 * use a system-wide <code>TimeBase</code>, often based on
 * a time-of-day clock.
 * <code>Manager</code> provides access to the system <code>TimeBase</code>
 * through <code>getSystemTimeBase</code>.
 *
 * <h2>Cloning and Merging DataSources</h2>
 *
 * <code>DataSources</code> can be cloned or merged.  If a
 * <code>DataSource</code> is cloned, more than one 
 * <code>MediaHandler</code> can use it as input.  Merging more than one
 * <code>DataSources</code> will generate one <code>DataSource</code> which
 * contains all the <code>SourceStreams</code> of the constituent
 * <code>DataSources</code><p> 
 * 
 * The <code>Manager</code> provides two methods:
 * <code>createCloneableDataSource</code> and
 * <code>createMergingDataSource</code> for such purpose.
 * <p>
 *
 * <h2>Manager Hints</h2>
 *
 * Using the <code>setHint</code> method, the preference for how
 * the <code>Manager</code> creates the objects can be specified.
 * However, a particular implementation of the <code>Manager</code>
 * can choose to ignore the requested hints.
 * @see #MAX_SECURITY
 * @see #CACHING
 * @see #LIGHTWEIGHT_RENDERER
 * @see #PLUGIN_PLAYER
 * <p>
 *
 * 
 * @since 1.0 , new methods added in 2.0
 * @see java.net.URL
 * @see MediaLocator
 * @see PackageManager
 * @see javax.media.protocol.DataSource
 * @see javax.media.protocol.URLDataSource
 * @see MediaHandler
 * @see Player
 * @see Processor
 * @see MediaProxy
 * @see TimeBase
 *
 * @version 2.0, 98/05/18.
 */

public final class Manager {

    private static String VERSION = "2.1.1e";

    /**
     * Boolean hint to turn on/off maximum security.
     */
    public static final int MAX_SECURITY = 1;

    /**
     * Boolean hint to turn on/off the use of playback caching.
     */
    public static final int  CACHING = 2;

    /**
     * Boolean hint to turn on/off the use of light weight rendering.
     * If on, the <code>Manager</code> will try to create
     * <code>Players</code> that use renderers which can interoperate with 
     * light weight GUI components.
     */
    public static final int  LIGHTWEIGHT_RENDERER = 3;

    /**
     * Boolean hint to request the <code>Manager</code> to create
     * <code>Players</code> that support
     * <code>PlugIns</code>.  Such <code>Players</code> use 
     * <code>PlugIns</code> to demultiplex, decode, render or multiplex 
     * the data.  It will also support <code>TrackControls</code> for
     * application level programming.
     */
    public static final int  PLUGIN_PLAYER = 4;


    private static int numberOfHints = 4; // Update this as you add more hints

    private static SystemTimeBase sysTimeBase = null;
    
    public final static String UNKNOWN_CONTENT_NAME = "unknown";

    private static boolean jdkInit = false;
    private static Method forName3ArgsM;
    private static Method getSystemClassLoaderM;
    private static ClassLoader systemClassLoader;
    private static Method getContextClassLoaderM;

    private static String fileSeparator = System.getProperty("file.separator");
    private static Hashtable hintTable = new Hashtable();
    static {
	/* Default values for the hints */
	hintTable.put(new Integer(MAX_SECURITY), new Boolean(false));
	hintTable.put(new Integer(CACHING), new Boolean(true));
	hintTable.put(new Integer(LIGHTWEIGHT_RENDERER), new Boolean(false));
	hintTable.put(new Integer(PLUGIN_PLAYER), new Boolean(false));
    }
    /**
     * This private constructor keeps anyone from actually
     * getting a <CODE>Manager</CODE>.
     */
    private Manager() {}

    /**
     * Returns the version string for this revision of JMF.
     */
    public static String getVersion() {
	return VERSION;
    }

    /**
     * Create a <CODE>Player</CODE> for the specified media.
     * This creates a MediaLocator from the URL and then
     * calls <CODE>createPlayer</CODE>.
     *
     * @param sourceURL The <CODE>URL</CODE> that describes the media data.
     * @return A new <CODE>Player</CODE>.
     * @exception NoPlayerException Thrown if no <CODE>Player</CODE>
     * can be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    public static Player createPlayer(URL sourceURL)
	throws IOException, NoPlayerException {
	return createPlayer(new MediaLocator(sourceURL));
    }

    /**
     * Create a <code>Player</code> for the specified media.
     * <p>
     * The algorithm for creating a <CODE>Player</CODE> from
     * a <code>MediaLocator</code> is:
     * <ol>
     * <li>Get the protocol from the <code>MediaLocator</code>.
     * <li>Get a list of <code>DataSource</code> classes that
     * support the protocol, using the protocol package-prefix-list.
     * <li> For each source class in the list:
     * <ol>
     * <li>Instantiate a new <code>DataSource</code>,
     * <li>Call the <code>connect</code> method to connect the source.
     * <li>Get the media content-type-name (using <code>getContentType</code>)
     * from the source.
     * <li>Get a list of <code>MediaHandler</code> classes that support the
     * media-content-type-name, using the content package-prefix-list.
     * <li>For each <code>MediaHandler</code> class in the list:
     * <ol>
     * <li>Instantiate a new <code>MediaHandler</code>.
     * <li>Attach the source to the <code>MediaHandler</code> by calling
     * <code>MediaHandler.setSource</code>.
     * <li>If there are no failures, determine the type of
     * the <code>MediaHandler</code>; otherwise try the next
     * <coded>MediaHandler</code> in the list.
     * <li>If the <code>MediaHandler</code> is a <code>Player</code>,
     * return the new <code>Player</code>.
     * <li>If the <code>MediaHandler</code> is a <code>MediaProxy</code>,
     * obtain a new <code>DataSource</code> from the <code>MediaProxy</code>,
     * obtain the list of <code>MediaHandlers</code> that support the new
     * <code>DataSource</code>, and continue searching the new list.
     * </ol>
     * <li>If no <code>MediaHandler</code> is found for this source,
     * try the next source in the list.
     * </ol>
     * <li>If no <code>Player</code> is found after trying all of the sources,
     * reuse the source list.<br>
     * This time, for each source class in the list:
     * <ol>
     * <li>Instantiate the source.
     * <li>Call the <code>connect</code> method to connect to the source.
     * <li>Use the content package-prefix-list to create a list of 
     * <code>MediaHandler</code> classes that support the
     * "unknown" content-type-name.
     * <li>For each <code>MediaHandler</code> class in the list,
     * search for a <code>Player</code> as in the previous search.
     * <ol>
     * <li>If no <code>Player</code> is found after trying all of the sources,
     * a <CODE>NoPlayerException</CODE> is thrown.
     * </ol>
     * </ol>
     * @param sourceLocator A <CODE>MediaLocator</CODE> that describes
     * the media content.
     * @return A <CODE>Player</CODE> for the media described by the source.
     * @exception NoPlayerException Thrown if no <CODE>Player</CODE> can
     * be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    public static Player createPlayer(MediaLocator sourceLocator)
	throws IOException, NoPlayerException {

        Player newPlayer = null;
	Hashtable sources = new Hashtable(10); // A repository of connected sources.
	    
	boolean needPluginPlayer = ((Boolean)Manager.getHint(PLUGIN_PLAYER)).booleanValue();

	// For RTP, the "non-plugin" player
	// (com.sun.media.content.rtp.Handler) actually supports plugin.
	String protocol = sourceLocator.getProtocol();
	if (protocol != null && 
	    (protocol.equalsIgnoreCase("rtp") || protocol.equalsIgnoreCase("rtsp")))
	    needPluginPlayer = false;

	try {
	    newPlayer = createPlayerForContent(sourceLocator, needPluginPlayer, sources);
	} catch (NoPlayerException e) {
	    // ... and if that doesn't work, try finding
	    // a player for the UNKNOWN_CONTENT_NAME.

	    if (needPluginPlayer)
		throw e;
	    newPlayer = createPlayerForContent(sourceLocator, true, sources);
	}

	// Disconnect the unsed sources
	if (sources.size() != 0) {
	    Enumeration enum = sources.elements();
	    while (enum.hasMoreElements()) {
		DataSource ds = (DataSource)enum.nextElement();
		ds.disconnect();
	    }
	}

	return newPlayer;
    }

    /**
     * Create a <code>Player</code> for the <code>DataSource</code>.
     * <p>
     * The algorithm for creating a <CODE>Player</CODE> from
     * a <code>DataSource</code> is:
     * <ol>
     * <li>Get the media content-type-name from the source by
     * calling <code>getContentType</code>.
     * <li>Use the content package-prefix-list to get a list of 
     * <code>Player</code> classes that support the media content-type name.
     * <li>For each <code>Player</code> class in the list:
     * <ol>
     * <li>Instantiate a new <code>Player</code>.
     * <li>Attach the source to the <code>Player</code> by calling
     * <code>setSource</code> on the <code>Player</code>.
     * <li>If there are no failures,  return the new <code>Player</code>;
     * otherwise,
     * try the next <code>Player</code> in the list.
      </ol>
     * <li>If no <code>Player</code> is found for this source:
     * <ol>
     * <li>Use the content package-prefix-list to create a list 
     * of <code>Player</code> classes that support the
     * "unknown" content-type-name.
     * <li>For each <code>Player</code> class in the list:
     * <ol>
     * <li>Instantiate a new <code>Player</code>.
     * <li>Attach the source to the <code>Player</code> by
     * calling <code>setSource</code> 
     * on the <code>Player</code>.
     * <li>If there are no failures, return the new <code>Player</code>;
     * otherwise, try the next <code>Player</code> in the list.
     * </ol>
     * </ol>
     * <li>If no <code>Player</code> can be created,
     * a <CODE>NoPlayerException</CODE> is thrown.
     * </ol>
     * @param DataSource The <CODE>DataSource</CODE> that describes
     * the media content.
     * @return A new <code>Player</code>.
     * @exception NoPlayerException Thrown if a <code>Player</code> can't
     * be created.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    public static Player createPlayer(DataSource source)
	throws IOException, NoPlayerException {

	Player newPlayer;
	boolean needPluginPlayer = ((Boolean)Manager.getHint(PLUGIN_PLAYER)).booleanValue();
	String contentType = source.getContentType();

	// For RTP, the "non-plugin" player
	// (com.sun.media.content.rtp.Handler) actually supports plugin.
	if (contentType != null && 
	    (contentType.equalsIgnoreCase("rtp") || contentType.equalsIgnoreCase("rtsp")))
	    needPluginPlayer = false;

	try {
	    // First try and create one using the source
	    // as the content identifier ...
	    if (needPluginPlayer)
		contentType = UNKNOWN_CONTENT_NAME;
	    newPlayer = createPlayerForSource(source, contentType, null);
	} catch( NoPlayerException e) {
	    // ... if that doesn't work use the unknown-content type.
	    if (needPluginPlayer)
		throw e;
	    newPlayer = createPlayerForSource(source, UNKNOWN_CONTENT_NAME, null);
	}

	return newPlayer;
    }

    /**
     * Create a Realized <code>Player</code> for the specified media.
     * <p>
     * This is a blocking method that creates a <code>Player</code>, calls
     * realize on it and returns only after the <code>Player</code> has
     * been Realized. Realizing a <code>Player</code> could be a time consuming
     * operation and one should use caution when using this method as it
     * could block the thread for several seconds.
     * @param ml The <code>MediaLocator</code> that describes the source of
     * the media.
     * @return A new <code>Player</code> that is in the <code>Realized</code>
     * state.
     * @exception NoPlayerException Thrown if a <code>Player</code> can't
     * be created.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     * @exception CannotRealizeException Thrown if there was a problem 
     * realizing the Player.
     */
    public static Player createRealizedPlayer(URL sourceURL)
	throws IOException, NoPlayerException, CannotRealizeException {
	Player p = createPlayer(sourceURL);
	blockingCall(p, Controller.Realized);
	return p;
    }

    /**
     * Create a Realized <code>Player</code> for the specified media.
     * <p>
     * This is a blocking method that creates a <code>Player</code>, calls
     * realize on it and returns only after the <code>Player</code> has
     * been Realized. Realizing a <code>Player</code> could be a time consuming
     * operation and one should use caution when using this method as it
     * could block the thread for several seconds.
     * @param ml The <code>MediaLocator</code> that describes the source of
     * the media.
     * @return A new <code>Player</code> that is in the <code>Realized</code>
     * state.
     * @exception NoPlayerException Thrown if a <code>Player</code> can't
     * be created.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     * @exception CannotRealizeException Thrown if there was a problem 
     * realizing the Player.
     */
    public static Player createRealizedPlayer(MediaLocator ml)
	throws IOException, NoPlayerException, CannotRealizeException {
	Player p = createPlayer(ml);
	blockingCall(p, Controller.Realized);
	return p;
    }

    /**
     * Create a Realized <code>Player</code> for the specified source.
     * <p>
     * This is a blocking method that creates a <code>Player</code>, calls
     * realize on it and returns only after the <code>Player</code> has
     * been Realized. Realizing a <code>Player</code> could be a time consuming
     * operation and one should use caution when using this method as it
     * could block the thread for several seconds.
     * @param source The <code>DataSource</code> that describes the media
     * content.
     * @return A new <code>Player</code> that is in the <code>Realized</code>
     * state.
     * @exception NoPlayerException Thrown if a <code>Player</code> can't
     * be created.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     * @exception CannotRealizeException Thrown if there was a problem 
     * realizing the Player.
     */
    public static Player createRealizedPlayer(DataSource source)
	throws IOException, NoPlayerException, CannotRealizeException {
	Player p = createPlayer(source);
	blockingCall(p, Controller.Realized);
	return p;
    }

    /**
     * Create a <code>Processor</code> for the specified media.
     * <p>
     * The algorithm is similar to that for creating a <code>Player</code>
     * from a <code>URL</code>
     * @param sourceURL A <CODE>URL</CODE> that describes
     * the media content.
     * @return A <CODE>Processor</CODE> for the media described by the source.
     * @exception NoProcessorException Thrown if no <CODE>Processor</CODE> can
     * be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    public static Processor createProcessor(URL sourceURL)
	throws IOException, NoProcessorException {
	return createProcessor(new MediaLocator(sourceURL));
    }

    /**
     * Create a <code>Processor</code> for the specified media.
     * <p>
     * The algorithm is similar to that for creating a <code>Player</code>
     * from a <code>MediaLocator</code>
     * @param sourceLocator A <CODE>MediaLocator</CODE> that describes
     * the media content.
     * @return A <CODE>Processor</CODE> for the media described by the source.
     * @exception NoProcessorException Thrown if no <CODE>Processor</CODE> can
     * be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    public static Processor createProcessor(MediaLocator sourceLocator)
	throws IOException, NoProcessorException {

        Processor newProcessor = null;
	Hashtable sources = new Hashtable(10); // A repository of connected sources.
	    
	try {
	    newProcessor = createProcessorForContent(sourceLocator, 
					false, sources);
	} catch (NoProcessorException e) {
	    // ... and if that doesn't work, try finding
	    // a player for the UNKNOWN_CONTENT_NAME.
	    newProcessor = createProcessorForContent(sourceLocator, 
					true, sources);
	}

	// Disconnect the unsed sources
	if (sources.size() != 0) {
	    Enumeration enum = sources.elements();
	    while (enum.hasMoreElements()) {
		DataSource ds = (DataSource)enum.nextElement();
		ds.disconnect();
	    }
	}

	return newProcessor;
    }

    /**
     * Create a <code>Processor</code> for the <code>DataSource</code>.
     * <p>
     * The algorithm for creating a <CODE>Processor</CODE> is similar
     * to creating a <code>Player</code> from a <code>DataSource</code>.
     * @param DataSource The <CODE>DataSource</CODE> that describes
     * the media content.
     * @return A new <code>Processor</code>.
     * @exception NoProcessorException Thrown if a <code>Processor</code> can't
     * be created.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    public static Processor createProcessor(DataSource source)
	throws IOException, NoProcessorException {

	Processor newProcessor;
	try {
	    // First try and create one using the source
	    // as the content identifier ...
	    newProcessor = createProcessorForSource(source, source.getContentType(), null);
	} catch( NoProcessorException e) {
	    // ... if that doesn't work use the unknown-content type.
	    newProcessor = createProcessorForSource(source, UNKNOWN_CONTENT_NAME, null);
	}

	return newProcessor;
    }

    /**
     * Create a Realized <code>Processor</code> for the specified
     * <code>ProcessorModel</code>.
     * <P>
     * This method accepts a <code>ProcessorModel</code> that describes
     * the required input and/or output format of the media data. It is a
     * blocking method and returns only after the <code>Processor</code>
     * reaches the <code>Realized</code> state.
     * @see ProcessorModel
     * @param model The <code>ProcessorModel</code> that describes the input and
     * output media
     * @return A new <code>Processor</code> that is in the <code>Realized</code>
     * state.
     * @exception NoProcessorException Thrown if a <code>Processor</code> can't
     * be created.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     * @exception CannotRealizeException Thrown if there was a problem 
     * realizing the Player.
     */
    public static Processor createRealizedProcessor(ProcessorModel model)
	throws IOException, NoProcessorException, CannotRealizeException {

	DataSource ds = null;
	MediaLocator ml = null;
	Processor processor = null;
	ContentDescriptor [] cds;
	ContentDescriptor rcd;
	boolean matched = false;
	Format [] reqFormats = null;
	Format    prefFormat;
	int    reqNumTracks = -1;
	int [] procToReqMap;
	TrackControl [] procTracks;
	int    i, j, k;
	int    nTracksEnabled = 0;
	boolean [] enabled;
	
	if (model == null)
	    throw new NoProcessorException("null ProcessorModel");

	// Figure out if we should use a datasource or a media locator as the
	// source. DataSource takes precedence over MediaLocator.
	ds = model.getInputDataSource();
	if (ds != null) {
	    processor = Manager.createProcessor(ds);
	} else if ((ml = model.getInputLocator()) != null) {
	    processor = Manager.createProcessor(ml);
	} else {
	    // Capture sources...
	    int nDevices = getNTypesOfCaptureDevices();
	    Vector dataSourceList = new Vector(1);
	    reqNumTracks = model.getTrackCount(nDevices);
	    reqFormats = new Format[reqNumTracks];
	    for (i = 0; i < reqNumTracks; i++) {
		reqFormats[i] = model.getOutputTrackFormat(i);
		// Look for a direct match
		Vector deviceList = CaptureDeviceManager.getDeviceList(reqFormats[i]);
		if (deviceList == null || deviceList.size() == 0) {
		    // No direct match. Find a similar device and hope to transcode
		    if (reqFormats[i] instanceof AudioFormat)
			deviceList = CaptureDeviceManager.getDeviceList(
							      new AudioFormat(null));
		    else if (reqFormats[i] instanceof VideoFormat)
			deviceList = CaptureDeviceManager.getDeviceList(
							      new VideoFormat(null));
		}

		if (deviceList.size() != 0) {
		    CaptureDeviceInfo cdi = (CaptureDeviceInfo) deviceList.elementAt(0);
		    if (cdi != null && cdi.getLocator() != null) {
			try {
			    DataSource crds = Manager.createDataSource(cdi.getLocator());
			    if (crds instanceof CaptureDevice) {
				// Set the format
				FormatControl [] fc = ((CaptureDevice)crds).getFormatControls();
				if (fc.length > 0) {
				    Format [] supported = fc[0].getSupportedFormats();
				    if (supported.length > 0) {
					for (int f = 0; f < supported.length; f++) {
					    if (supported[f].matches(reqFormats[i])) {
						Format intersect =
						    supported[f].intersects(reqFormats[i]);
						if (intersect != null) {
						    if (fc[0].setFormat(intersect) != null)
							break;
						}
					    }
					}
				    }
				}
			    }
			    // ds.connect(); ???
			    dataSourceList.addElement(crds);
			} catch (IOException ioe) {
			} catch (NoDataSourceException ndse) {
			}
		    }
		}
	    }
	    
	    if (dataSourceList.size() == 0) {
		throw new NoProcessorException("No suitable capture devices found!");
	    } else if (dataSourceList.size() > 1) {
		// Merge the datasources
		DataSource [] dataSourceArray = new DataSource[dataSourceList.size()];
		for (k = 0; k < dataSourceList.size(); k++)
		    dataSourceArray[k] = (DataSource) dataSourceList.elementAt(k);
		try {
		    ds = Manager.createMergingDataSource(dataSourceArray);
		} catch (IncompatibleSourceException ise) {
		    throw new NoProcessorException("Couldn't merge capture devices");
		}
	    } else
		ds = (DataSource) dataSourceList.elementAt(0);
	    processor = Manager.createProcessor(ds);
	}

	if (processor == null)
	    throw new NoProcessorException("Couldn't create Processor for source");

	// Configure the processor
	blockingCall(processor, Processor.Configured);

	// Content descriptor stuff
	rcd = model.getContentDescriptor();
	if (rcd == null) {
	    processor.setContentDescriptor(null);
	} else {
	    cds = processor.getSupportedContentDescriptors();
	    if (cds == null || cds.length == 0)
		throw new NoProcessorException("Processor doesn't support output");
	    
	    for (i = 0; i < cds.length; i++) {
		if (rcd.matches(cds[i])) {
		    if (processor.setContentDescriptor(cds[i]) != null) {
			matched = true;
			break;
		    } 
		}
	    }
	    if (!matched)
		throw new NoProcessorException("Processor doesn't support requested " +
					       "output ContentDescriptor");
	    
	}

	procTracks = processor.getTrackControls();
	if (procTracks != null && procTracks.length > 0) {
	    // Format stuff
	    int nValidTracks = 0;
	    for (i = 0; i < procTracks.length; i++) {
		if (procTracks[i].isEnabled())
		    nValidTracks++;
	    }
	    if (reqNumTracks == -1)
		reqNumTracks = model.getTrackCount(nValidTracks);
	    if (reqNumTracks > 0) {
		if (reqFormats == null) 
		    reqFormats = new Format[reqNumTracks];
		procToReqMap = new int[reqNumTracks];     // Whats the proc's trackNo for a
						      // requested track no.
		for (i = 0; i < reqNumTracks; i++) {
		    if (reqFormats[i] == null)
			reqFormats[i] = model.getOutputTrackFormat(i);
		    procToReqMap[i] = -1;
		}
		
		enabled = new boolean[procTracks.length];
		// First try the default track formats.
		for (i = 0; i < procTracks.length; i++) {
		    enabled[i] = false;
		    if (!procTracks[i].isEnabled())
			continue;
		    prefFormat = procTracks[i].getFormat();
		    for (j = 0; j < reqNumTracks; j++) {
			if ( procToReqMap[j] == -1 &&
			     ( reqFormats[j] == null ||
			       prefFormat.matches(reqFormats[j]) ) ) {
			    
			    if (model.isFormatAcceptable(j, prefFormat)) {
				procToReqMap[j] = i;
				enabled[i] = true;
				nTracksEnabled++;
				//procTracks[i].setFormat(prefFormat); // no need to do this
				break;
			    }
			}
		    }
		}
		
		for (i = 0; i < procTracks.length && nTracksEnabled < reqNumTracks; i++) {
		    boolean used = false;
		    Format [] suppFormats;
		    // If not enabled by processor, its not to be used
		    if (!procTracks[i].isEnabled())
			continue;
		    // Check if its already been matched with one of the requested
		    for (j = 0; j < reqNumTracks; j++)
			if (procToReqMap[j] == i)
			    used = true;
		    if (used)
			continue;
		    
		    // Get all the supported formats for this track (transcode)
		    suppFormats = procTracks[i].getSupportedFormats();
		    if (suppFormats == null || suppFormats.length == 0)
			continue;
		    
		    matched = false;
		    for (k = 0; k < suppFormats.length && !matched; k++) {
			prefFormat = suppFormats[k];
			for (j = 0; j < reqNumTracks && !matched; j++) {
			    //System.err.println("trying " + prefFormat + " && " + reqFormats[j]);   
			    if ( procToReqMap[j] == -1 &&
				 ( reqFormats[j] == null ||
				   prefFormat.matches(reqFormats[j]) ) ) {
				
				if (model.isFormatAcceptable(j, prefFormat)) {
				    if (procTracks[i].setFormat(prefFormat) != null) {
					procToReqMap[j] = i;
					enabled[i] = true;
					nTracksEnabled++;
					matched = true;
					break;
				    }
				}
			    }
			    
			}
		    }
		}
		
		if (nTracksEnabled < reqNumTracks) {
		    // What should we do if all requested tracks are not satisfied?
		    // Is failing the right thing to do
		    throw new CannotRealizeException("Unable to provide all " +
						     "requested tracks");
		}
	    }
	}

	blockingCall(processor, Controller.Realized);

	return processor;
    }

    /**
     * Create a <code>DataSource</code> for the specified media.
     *
     * @param sourceURL The <CODE>URL</CODE> that describes the media data.
     * @return A new <CODE>DataSource</CODE> for the media.
     * @exception NoDataSourceException Thrown if no <code>DataSource</code>
     * can be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    public static DataSource createDataSource(URL sourceURL)
	throws IOException, NoDataSourceException  {
	return
	    createDataSource(new MediaLocator(sourceURL));
    }
    
    /**
     * Create a <code>DataSource</code> for the specified media.
     * <p>
     *
     * Returns a data source for the protocol specified by
     * the <CODE>MediaLocator</CODE>. The returned data source
     * is <i>connected</i>; <code>DataSource.connect</code> has been
     * invoked.
     * <p>
     *
     * The algorithm for creating a <code>DataSource</code> from
     * a <code>MediaLocator</code> is:
     * <ol>
     * <li>Get the protocol from the <code>MediaLocator</code>.
     * <li>Use the protocol package-prefix list to get a list of 
     * <code>DataSource</code> classes that
     * support the protocol.
     * <li> For each source class in the list:
     * <ol>
     * <li>Instantiate a new <code>DataSource</code>.
     * <li>Call <code>connect</code> to connect the source.
     * <li>If there are no errors, return the connected
     * source; otherwise, try the next source in the list.
     * </ol>
     * <li>If no source has been found, obtain a <code>URL</code> from the
     * <code>MediaLocator</code> and use it to create
     * a <code>URLDataSource</code>
     * <li>If no source can be found, a <CODE>NoDataSourceException</CODE>
     * is thrown.
     * </ol>
     * 
     *
     * @param sourceLocator The source protocol for the media data.
     * @return A connected <CODE>DataSource</CODE>.
     * @exception NoDataSourceException Thrown if no <CODE>DataSource</CODE>
     * can be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    static public DataSource createDataSource(MediaLocator sourceLocator)
	throws IOException, NoDataSourceException {

	DataSource source = null;

	// For each DataSource that implements the protocol
        // that's specified in the source locator ....
	Enumeration protoList =
	    getDataSourceList(sourceLocator.getProtocol()).elements();
	while(protoList.hasMoreElements()) {
	    
	    String protoClassName = (String)protoList.nextElement();
	    try {

		// ... Try and instance a DataSource ....
		Class protoClass = getClassForName(protoClassName);
		source = (DataSource)protoClass.newInstance();

		// ... and get it connected ....
		source.setLocator(sourceLocator);
		source.connect();

		//  ... o.k. we've found one, so we're done.
		break;
		
	    } catch (ClassNotFoundException e) {
		// try another data source.
		source = null;
	    } catch (InstantiationException e) {
		// try another data source.
		source = null;
	    } catch (IllegalAccessException e) {
		// try another data source.
		source = null;
	    } catch (Exception e) {
		source = null;
		String err = "Error instantiating class: " + protoClassName + " : " + e;
		Log.error(e);
		throw new NoDataSourceException(err);
	    } catch (Error e) {
		source = null;
		String err = "Error instantiating class: " + protoClassName + " : " + e;
		Log.error(e);
		throw new NoDataSourceException(err);
	    }
	}
	
	// If we haven't found one installed,
	// we'll try and create one from a URL/URLDataSource.
// TEMPORARILY COMMENTED OUT URLDataSource: 1/15/99
// 	if( source == null) {
// 	    try {
// 		source = new URLDataSource(sourceLocator.getURL());
// 		source.connect();
// 	    } catch(MalformedURLException me) {
// 		// Can't get a URL so we're done.
// 		source = null;
// 	    }
// 	}
	
        // If we still haven't found one, we're done
	// and we don't have a source.
	if( source == null) {
	    throw new NoDataSourceException("Cannot find a DataSource for: " + sourceLocator);
	}

	Log.comment("DataSource created: " + source + "\n");

	return source;
    }
    
    /**
     * Creates a merged <code>DataSource</code> from an array of sources. 
     * All sources must be of the same type (i.e <code>PullDataSource</code>,
     * <code>PushDataSource</code>, etc.) otherwise an
     * IncompatibleSourceException is thrown.
     * The returned <code>DataSource</code> is of the same type of 
     * the given sources. Its content-type is RAW if all sources are of 
     * type RAW. Otherwise, its content-type is MIXED. 
     *
     * @param sources the <code>DataSources</code> to be merged
     * @return  a <code>DataSource</code> which contains all the streams 
     * of the original sources
     * @exception IncompatibleSourceException  if the sources are not 
     * of the same type
     */
    static public DataSource createMergingDataSource(DataSource[] sources) 
	throws IncompatibleSourceException {
	
	// check if the sources type matches
	if (sources.length == 0)
	    throw new IncompatibleSourceException("No sources");

	if (sources[0] instanceof PullDataSource) {
	    for (int i = 1; i < sources.length; i ++) {
		if (!(sources[i] instanceof PullDataSource))
		    throw new IncompatibleSourceException("One of the sources isn't matching the others"); 
	    }
	    PullDataSource pds[] = new PullDataSource[sources.length];
	    for (int i = 0; i < pds.length; i++)
		pds[i] = (PullDataSource)sources[i];
	    return reflectMDS("com.ibm.media.protocol.MergingPullDataSource", pds);
	}

	if (sources[0] instanceof PushDataSource) {
	    for (int i = 1; i < sources.length; i ++) {
		if (!(sources[i] instanceof PushDataSource))
		    throw new IncompatibleSourceException("One of the sources isn't matching the others"); 
	    }
	    PushDataSource pds[] = new PushDataSource[sources.length];
	    for (int i = 0; i < pds.length; i++)
		pds[i] = (PushDataSource)sources[i];
	    return reflectMDS("com.ibm.media.protocol.MergingPushDataSource",pds);
	}

	if (sources[0] instanceof PullBufferDataSource) {
	    for (int i = 1; i < sources.length; i ++) {
		if (!(sources[i] instanceof PullBufferDataSource))
		    throw new IncompatibleSourceException("One of the sources isn't matching the others"); 
	    }
	    PullBufferDataSource pds[] = new PullBufferDataSource[sources.length];
	    for (int i = 0; i < pds.length; i++)
		pds[i] = (PullBufferDataSource)sources[i];
	    return reflectMDS("com.ibm.media.protocol.MergingPullBufferDataSource",pds);
	}
	
	if (sources[0] instanceof PushBufferDataSource) {
	    boolean anyCapture = false;
	    for (int i = 1; i < sources.length; i ++) {
		if (!(sources[i] instanceof PushBufferDataSource))
		    throw new IncompatibleSourceException("One of the sources isn't matching the others");
		if (sources[i] instanceof CaptureDevice)
		    anyCapture = true;
	    }
	    PushBufferDataSource pds[] = new PushBufferDataSource[sources.length];
	    for (int i = 0; i < pds.length; i++)
		pds[i] = (PushBufferDataSource)sources[i];
	    if (anyCapture)
		return reflectMDS("com.ibm.media.protocol.MergingCDPushBDS",pds);
	    else
		return reflectMDS("com.ibm.media.protocol.MergingPushBufferDataSource",pds);
	}
	
	return null;
    }
    
    /**
     * Creates a cloneable <code>DataSource</code>. The returned 
     * <code>DataSource</code> implements the <code>SourceCloneable</code> 
     * interface and enables the creation of clones by the createClone method.
     * <br> 
     * If the input <code>DataSource</code> implements
     * <code>SourceCloneable</code>, it will be returned right away
     * as the result.  Otherwise, a "proxy" <code>DataSource</code> is
     * created.  It implements the <code>SourceCloneable</code> interface and
     * can be used to generate other clones.
     * <br>
     * When <code>createCloneableDataSource</code> is called on a
     * <code>DataSource</code>, the returned <code>DataSource</code> should
     * be used in place of the original <code>DataSource</code>.  Any
     * attempt to use the original <code>DataSource</code> may generate
     * unpredictable results.
     * <br>
     * The resulted cloneable <code>DataSource</code> can be used to
     * generate clones.  The clones generated may or may not has the
     * same properties of the original DataSource depending on the
     * implementation.  Therefore, they should be checked against the
     * properties required for the application.  If the original
     * <code>DataSource</code> is not SourceCloneable and a "proxy"
     * <code>DataSource</code> is resulted, the clones generated from
     * this "proxy" <code>DataSource</code> is of type
     * <code>PushDataSource or PushBufferDataSource</code> depending on 
     * the type of the original <code>DataSource</code>.  In this case,
     * each clone pushes data at the same rate that the original 
     * <code>DataSource</code> is pulled or pushed.
     *
     * @see SourceCloneable
     * @param source the DataSource to be cloned
     * @return a cloneable DataSource for the given source
     */

    static private DataSource reflectMDS(String cname, Object pds) {
	Class cls;
	Constructor cc;
	Class[] paramTypes = new Class[1];
	Object[] arg = new Object[1];

	try {
	    cls = Class.forName(cname);
	    paramTypes[0] = pds.getClass();
	    
	    cc = cls.getConstructor(paramTypes);

	    if ( cname.indexOf("PullDataSource") >= 0 ) {
		arg[0] = (PullDataSource[])pds;
	    } else if (cname.indexOf("PushDataSource") >= 0) {
		arg[0] = (PushDataSource[])pds;
	    } else if (cname.indexOf("PullBufferDataSource") >= 0) {
		arg[0] = (PullBufferDataSource[])pds;
	    } else if (cname.indexOf("PushBufferDataSource") >= 0) {
		arg[0] = (PushBufferDataSource[])pds;
	    } else if ( cname.indexOf("CDPushBDS") >= 0 ) {
		arg[0] = (PushBufferDataSource[])pds;
	    }

	    return (DataSource)(cc.newInstance(arg));
	} catch (Exception ex) {
	}

	return null;
    }
    
    static private DataSource reflectDS(String cname, DataSource source) {
	Class cls;
	Constructor cc;
	Class[] paramTypes = new Class[1];
	Object[] arg = new Object[1];

	try {
	    cls = Class.forName(cname);

	    if ( cname.indexOf("PullDataSource") >= 0) {
		paramTypes[0] = PullDataSource.class;
		arg[0] = (PullDataSource)source;
	    } else if ( cname.indexOf("PushDataSource") >= 0) {
		paramTypes[0] = PushDataSource.class;
		arg[0] = (PushDataSource)source;
	    } else if ( cname.indexOf("PullBufferDataSource") >= 0) {
		paramTypes[0] = PullBufferDataSource.class;
		arg[0] = (PullBufferDataSource)source;
	    } else if ( cname.indexOf("PushBufferDataSource") >= 0 ) {
		paramTypes[0] = PushBufferDataSource.class;
		arg[0] = (PushBufferDataSource)source;
	    }

	    cc = cls.getConstructor(paramTypes);
	    return (DataSource)(cc.newInstance(arg));
	} catch (Exception ex) {
	    // return null;
	}
	
	return null;
    }

    static public DataSource createCloneableDataSource(DataSource source) {

	if (source instanceof SourceCloneable)
	    return source;

	
	if (source instanceof CaptureDevice) {
	    // the created clone will support CaptureDevice interface
	    if (source instanceof javax.media.protocol.PullDataSource) 
		return reflectDS("com.ibm.media.protocol.CloneableCapturePullDataSource", source);
	    
	    if (source instanceof javax.media.protocol.PushDataSource) 
		return reflectDS("com.ibm.media.protocol.CloneableCapturePushDataSource", source);
	    
	    if (source instanceof javax.media.protocol.PullBufferDataSource)
		return reflectDS("com.ibm.media.protocol.CloneableCapturePullBufferDataSource", source);
	    
	    if (source instanceof javax.media.protocol.PushBufferDataSource)
		return reflectDS("com.ibm.media.protocol.CloneableCapturePushBufferDataSource", source);
	}
	
	// Otherwise create a regular non-capture DataSource
	if (source instanceof javax.media.protocol.PullDataSource) 
	    return reflectDS("com.ibm.media.protocol.CloneablePullDataSource", source);
	
	if (source instanceof javax.media.protocol.PushDataSource) 
	    return reflectDS("com.ibm.media.protocol.CloneablePushDataSource", source);

	if (source instanceof javax.media.protocol.PullBufferDataSource) 
	    return reflectDS("com.ibm.media.protocol.CloneablePullBufferDataSource", source);
	
	if (source instanceof javax.media.protocol.PushBufferDataSource)
	    return reflectDS("com.ibm.media.protocol.CloneablePushBufferDataSource", source);
	
	return null;
    }

    /**
     * Get the time-base object for the system.
     * @return The system time base.
     */
    public static TimeBase getSystemTimeBase() {
	if (sysTimeBase == null) {
	    sysTimeBase = new SystemTimeBase();
	}
	return sysTimeBase;
    }

    /**
     * Create a player for the <CODE>MediaLocator</CODE>.
     * <p>
     * If <CODE>useUnknownContent</CODE> is <CODE>true</CODE>,
     * a <CODE>Player</CODE> for
     * content-type  <CODE>UNKNOWN_CONTENT_NAME</CODE> is created; otherwise,
     * the <CODE>DataSource</CODE> determines the content-type with the
     * <code>getContentType</code> method.
     *
     * @param sourceLocator Used to determine the protocol that
     * the <CODE>DataSource</CODE> will use.
     * @param useKnownContent Used to determine the content type used
     * to find a <CODE>Player</CODE>.
     * @returns A new <CODE>Player</CODE>.
     * @exception NoPlayerException Thrown if no <CODE>Player</CODE> can
     * be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    static Player createPlayerForContent(MediaLocator sourceLocator,
			       boolean useUnknownContent, Hashtable sources)
	throws IOException, NoPlayerException {

	Player newPlayer = null;
	boolean sourceUsed[] = new boolean[1];
	sourceUsed[0] = false;	// A pass-by-referenced boolean to indicate
				// if the data source has been used.
				// If so, it need to be disconnected.
	    
	// For each DataSource that implements the protocol
        // that's specified in the source ...

	Enumeration protoList =
	    getDataSourceList(sourceLocator.getProtocol()).elements();
	while(protoList.hasMoreElements()) {
	    
	    String protoClassName = (String)protoList.nextElement();

	    DataSource source = null; // 
	    try {

		// Look into the registry to see if that DataSource
		// has already been created.
		if ((source = (DataSource)sources.get(protoClassName)) == null) {

		    // ... Try an instance a DataSource ....
		    Class protoClass = getClassForName(protoClassName);
		    source = (DataSource)protoClass.newInstance();

		    // ... and get it connected ....
		    source.setLocator(sourceLocator);
		    source.connect();
		} else
		    sources.remove(protoClassName);

		// ... o.k. we've found one, so now try and get
		// a Player for it.
		try {
		    if( useUnknownContent) {
			// Either use the default content type ...
			newPlayer = createPlayerForSource(source, 
					UNKNOWN_CONTENT_NAME, sourceUsed);
		    } else {
			// ... or let the source specify the content type.
			newPlayer =
			    createPlayerForSource(source, 
					source.getContentType(), sourceUsed);
		    }

		    // If we got one we're done.
		    break;
		    
		} catch (NoPlayerException e) {
		    // Go try another one.
		    newPlayer = null;
		}
		
		// No luck so try another source.
		if (sourceUsed[0])
		    source.disconnect();
		else
		    sources.put(protoClassName, source);
		
	    } catch (ClassNotFoundException e) {
		// try another data source.
		source = null;
	    } catch (InstantiationException e) {
		// try another one.
		source = null;
	    } catch (IllegalAccessException e) {
		// try another one.
		source = null;
	    } catch (Exception e) {
		source = null;
		String err = "Error instantiating class: " + protoClassName + " : " + e;
		Log.error(e);
		throw new NoPlayerException(err);
	    } catch (Error e) {
		source = null;
		String err = "Error instantiating class: " + protoClassName + " : " + e;
		Log.error(e);
		throw new NoPlayerException(err);
	    }
	}

// TEMPORARILY COMMENTED OUT URLDataSource: 1/15/99
	// If we don't have a Player yet, then try and create a Player
	// from the URL data source.
// 	DataSource source = null;
// 	sourceUsed[0] = false;
// 	if( newPlayer == null) {
// 	    try {

// 		if ((source = (DataSource)sources.get("javax.media.URLDataSource")) == null) {
// 		    source = new URLDataSource(sourceLocator.getURL());
// 		    source.connect();
// 		} else
// 		    sources.remove("javax.media.URLDataSource");

// 		// Got the data source so attach it to
// 		// a player.
// 		if( useUnknownContent) {
// 		    // Either use the default content type ...
// 		    newPlayer = createPlayerForSource(source, 
// 					UNKNOWN_CONTENT_NAME, sourceUsed);
// 		} else {
// 		    // ... or let the source specify the content type.
// 		    newPlayer =
// 			createPlayerForSource(source, 
// 					source.getContentType(), sourceUsed);
// 		}

// 	    } catch(MalformedURLException me) {
// 		// Can't get a URL so we're done.
// 		source = null;
// 	    } finally {
// 		if (source != null) {
// 		    if (sourceUsed[0])
// 			source.disconnect();
// 		    else
// 	        	sources.put("javax.media.URLDataSource", source);
// 		}
// 	    }
// 	}

	if( newPlayer == null)
	    throw new NoPlayerException("Cannot find a Player for :" + sourceLocator);

	return newPlayer;
    }


    /**
     * Create a <CODE>Player</CODE> for a particular content type
     * using the source.
     *
     * @param source The source of media for the <CODE>Player</CODE>.
     * @param contentTypeName The type of content the <CODE>Player</CODE>
     * should handle.
     * @return A new <CODE>Player</CODE>.
     * @exception NoPlayerException Thrown if no <CODE>Player</CODE> can
     * be found for the source and content-type.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    static Player createPlayerForSource(DataSource source, 
		String contentTypeName, boolean sourceUsed[])
		throws IOException, NoPlayerException {

	Player newPlayer = null;
	if (sourceUsed != null) sourceUsed[0] = true;

	// Try every handler we can find for this content type.
	Enumeration playerList =
	    getHandlerClassList(contentTypeName).elements();

	MediaHandler mHandler;
	DataSource newSource = null;
	while(playerList.hasMoreElements()) {
	    String handlerClassName = (String)playerList.nextElement();

	    try {
		
		// ... try and instance the handler ...
		Class handlerClass = getClassForName(handlerClassName);
		mHandler = (MediaHandler)handlerClass.newInstance();
		// ... set the DataSource on it ...
		mHandler.setSource(source);

		// if this is a Player then we're done.
		if( mHandler instanceof Player) {
		    newPlayer = (Player)mHandler;
		    break;
		}

		// Otherwise it must be a proxy.
		// Get a new data source, and content type  ...
		MediaProxy mProxy = (MediaProxy)mHandler;
		newSource = mProxy.getDataSource();
		String newContentType = newSource.getContentType();
		// .. recurse to try and create a Player with it.
		try{
		    newPlayer =
			createPlayerForSource(newSource,newContentType, null);
		}catch (NoPlayerException e){
		    newPlayer = createPlayerForSource(newSource,
						      UNKNOWN_CONTENT_NAME,
						      null);
		    if (newPlayer != null)
			break;
		}
	    
		
	    } catch (ClassNotFoundException e) {
		// Couldn't find the handler so try another.
		newPlayer = null;
		if (sourceUsed != null) sourceUsed[0] = false;
	    } catch (InstantiationException e) {
		// Can't instance the handler so try another.
		newPlayer = null;
		if (sourceUsed != null) sourceUsed[0] = false;
	    } catch (IllegalAccessException e) {
		// Can't get at the handler so try another.
		newPlayer = null;
		if (sourceUsed != null) sourceUsed[0] = false;
	    } catch (IncompatibleSourceException e) {
		// The handler didn't know what to
		// do with the DataSource so try another handler.
		newPlayer = null;
	    } catch (NoDataSourceException e) {
		// Proxy failed to produce a new data source
		// see if there are other proxies out there.
		newPlayer = null;
	    } catch (Exception e) {
		newPlayer = null;
		String err = "Error instantiating class: " + handlerClassName + " : " + e;
		throw new NoPlayerException(err);
	    } catch (Error e) {
		String err = "Error instantiating class: " + handlerClassName + " : " + e;
 		Log.error(e);
		throw new NoPlayerException(err);
	    }

	}

	if( newPlayer == null) {
	    throw new NoPlayerException("Cannot find a Player for: " + source);
	}

	Log.comment("Player created: " + newPlayer);
	Log.comment("  using DataSource: " + source + "\n");

	return newPlayer;
    }
    
    /**
     * Create a player for the <CODE>MediaLocator</CODE>.
     * <p>
     * If <CODE>useUnknownContent</CODE> is <CODE>true</CODE>,
     * a <CODE>Processor</CODE> for
     * content-type  <CODE>UNKNOWN_CONTENT_NAME</CODE> is created; otherwise,
     * the <CODE>DataSource</CODE> determines the content-type with the
     * <code>getContentType</code> method.
     *
     * @param sourceLocator Used to determine the protocol that
     * the <CODE>DataSource</CODE> will use.
     * @param useKnownContent Used to determine the content type used
     * to find a <CODE>Processor</CODE>.
     * @returns A new <CODE>Processor</CODE>.
     * @exception NoProcessorException Thrown if no <CODE>Processor</CODE> can
     * be found.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    static Processor createProcessorForContent(MediaLocator sourceLocator,
			       boolean useUnknownContent, Hashtable sources)
	throws IOException, NoProcessorException {

	Processor newProcessor = null;
	boolean sourceUsed[] = new boolean[1];
	sourceUsed[0] = false;	// A pass-by-referenced boolean to indicate
				// if the data source has been used.
				// If so, it need to be disconnected.
	    
	// For each DataSource that implements the protocol
        // that's specified in the source ...
	Enumeration protoList =
	    getDataSourceList(sourceLocator.getProtocol()).elements();
	while(protoList.hasMoreElements()) {
	    
	    String protoClassName = (String)protoList.nextElement();
	    DataSource source = null;
	    try {

		// Look into the registry to see if that DataSource
		// has already been created.
		if ((source = (DataSource)sources.get(protoClassName)) == null) {

		    // ... Try an instance a DataSource ....
		    Class protoClass = getClassForName(protoClassName);
		    source = (DataSource)protoClass.newInstance();

		    // ... and get it connected ....
		    source.setLocator(sourceLocator);
		    source.connect();
		} else
		    sources.remove(protoClassName);

		// ... o.k. we've found one, so now try and get
		// a Processor for it.
		try {
		    if( useUnknownContent) {
			// Either use the default content type ...
			newProcessor = createProcessorForSource(source, 
					UNKNOWN_CONTENT_NAME, sourceUsed);
		    } else {
			// ... or let the source specify the content type.
			newProcessor =
			    createProcessorForSource(source, 
					source.getContentType(), sourceUsed);
		    }

		    // If we got one we're done.
		    break;
		    
		} catch (NoProcessorException e) {
		    // Go try another one.
		    newProcessor = null;
		}
		
		// No luck so try another source.
		if (sourceUsed[0])
		    source.disconnect();
		else
		    sources.put(protoClassName, source);
		
	    } catch (ClassNotFoundException e) {
		// try another data source.
		source = null;
	    } catch (InstantiationException e) {
		// try another one.
		source = null;
	    } catch (IllegalAccessException e) {
		// try another one.
		source = null;
	    } catch (Exception e) {
		String err = "Error instantiating class: " + protoClassName + " : " + e;
		Log.error(e);
		throw new NoProcessorException(err);
	    } catch (Error e) {
		String err = "Error instantiating class: " + protoClassName + " : " + e;
		Log.error(e);
		throw new NoProcessorException(err);
	    }
	}

	// TEMPORARILY COMMENTED OUT URLDataSource: 1/15/99
	// If we don't have a Processor yet, then try and create a Processor
	// from the URL data source.
// 	DataSource source = null;
// 	sourceUsed[0] = false;
// 	if( newProcessor == null) {
// 	    try {

// 		if ((source = (DataSource)sources.get("javax.media.URLDataSource")) == null) {
// 		    source = new URLDataSource(sourceLocator.getURL());
// 		    source.connect();
// 		} else
// 		    sources.remove("javax.media.URLDataSource");

// 		// Got the data source so attach it to
// 		// a player.
// 		if( useUnknownContent) {
// 		    // Either use the default content type ...
// 		    newProcessor = createProcessorForSource(source, 
// 					UNKNOWN_CONTENT_NAME, sourceUsed);
// 		} else {
// 		    // ... or let the source specify the content type.
// 		    newProcessor =
// 			createProcessorForSource(source, 
// 					source.getContentType(), sourceUsed);
// 		}

// 	    } catch(MalformedURLException me) {
// 		// Can't get a URL so we're done.
// 		source = null;
// 	    } finally {
// 		if (source != null) {
// 		    if (sourceUsed[0])
// 			source.disconnect();
// 		    else
// 	        	sources.put("javax.media.URLDataSource", source);
// 		}
// 	    }
// 	}

	if( newProcessor == null)
	    throw new NoProcessorException("Cannot find a Processor for: " + sourceLocator);

	return newProcessor;
    }


    /**
     * Create a <CODE>Processor</CODE> for a particular content type
     * using the source.
     *
     * @param source The source of media for the <CODE>Processor</CODE>.
     * @param contentTypeName The type of content the <CODE>Processor</CODE>
     * should handle.
     * @return A new <CODE>Processor</CODE>.
     * @exception NoProcessorException Thrown if no <CODE>Processor</CODE> can
     * be found for the source and content-type.
     * @exception IOException Thrown if there was a problem connecting
     * with the source.
     */
    static Processor createProcessorForSource(DataSource source, 
		String contentTypeName, boolean sourceUsed[])
		throws IOException, NoProcessorException {

	Processor newProcessor = null;
	if (sourceUsed != null) sourceUsed[0] = true;

	// Try every handler we can find for this content type.
	Enumeration playerList =
	    getProcessorClassList(contentTypeName).elements();

	MediaHandler mHandler;
	DataSource newSource = null;
	while(playerList.hasMoreElements()) {
	    String handlerClassName = (String)playerList.nextElement();

	    try {
		
		// ... try and instance the handler ...
		Class handlerClass = getClassForName(handlerClassName);
		mHandler = (MediaHandler)handlerClass.newInstance();
		
		// ... set the DataSource on it ...
		mHandler.setSource(source);

		// if this is a Processor then we're done.
		if( mHandler instanceof Processor) {
		    newProcessor = (Processor)mHandler;
		    break;
		}

		// Otherwise it must be a proxy.
		// Get a new data source, and content type  ...
		MediaProxy mProxy = (MediaProxy)mHandler;
		newSource = mProxy.getDataSource();
		String newContentType = newSource.getContentType();

		// .. recurse to try and create a Player with it.
		try{
		    newProcessor =
			createProcessorForSource(newSource,newContentType, null);
		}catch (NoProcessorException e){
		    newProcessor = createProcessorForSource(newSource,
							 UNKNOWN_CONTENT_NAME,
							 null);
		    if (newProcessor != null)
			break;
		}
		
	    } catch (ClassNotFoundException e) {
		// Couldn't find the handler so try another.
		newProcessor = null;
		if (sourceUsed != null) sourceUsed[0] = false;
	    } catch (InstantiationException e) {
		// Can't instance the handler so try another.
		newProcessor = null;
		if (sourceUsed != null) sourceUsed[0] = false;
	    } catch (IllegalAccessException e) {
		// Can't get at the handler so try another.
		newProcessor = null;
		if (sourceUsed != null) sourceUsed[0] = false;
	    } catch (IncompatibleSourceException e) {
		// The handler didn't know what to
		// do with the DataSource so try another handler.
		newProcessor = null;
	    } catch (NoDataSourceException e) {
		// Proxy failed to produce a new data source
		// see if there are other proxies out there.
		newProcessor = null;
	    } catch (Exception e) {
		newProcessor = null;
		String err = "Error instantiating class: " + handlerClassName + " : " + e;
		Log.error(e);
		throw new NoProcessorException(err);
	    } catch (Error e) {
		newProcessor = null;
		String err = "Error instantiating class: " + handlerClassName + " : " + e;
		Log.error(e);
		throw new NoProcessorException(err);
	    }

	}

	if( newProcessor == null) {
	    throw new NoProcessorException("Cannot find a Processor for: " + source);
	}

	Log.comment("Processor created: " + newProcessor);
	Log.comment("  using DataSource: " + source + "\n");

	return newProcessor;
    }


    /**
     * Create a <code>DataSink</code> for the specified input
     * Datasource and destination  Medialocator.
     * <p>
     * 
     * The algorithm for creating a <CODE>DataSink</CODE> from
     * a <code>MediaLocator and datasource </code> is:
     * <ol>
     * <li>Get the protocol from the <code>MediaLocator</code>.
     * <li>Get a list of <code>MediaHandler</code> classes  within the
     * datasink package that support the
     * protocol, using the content package-prefix-list.
     * i.e. search for content-prefix.media.datasink.protocol.Handler
     * <li>For each <code>MediaHandler</code> class in the list:
     * <ol>
     * <li>Instantiate a new <code>MediaHandler</code>.
     * <li>If the <code>MediaHandler</code> is a
     * <code>DataSink</code>,
     * Attach the source to the <code>MediaHandler</code> by calling
     * <code>MediaHandler.setSource</code>
     * <li>If there are no failures, return this
     * <code>DataSink</code>;otherwise try the next
     * <code>MediaHandler</code> in the list.
     * 
     * <li>If the <code>MediaHandler</code> is a <code>DataSinkProxy</code>,
     * obtain the content type of the proxy using the getContentType() method.
     * Now obtain a list of <code>MediaHandlers</code> that support
     * the protocol of the Medialocator and the content type returned by
     * the proxy <BR> 
     * i.e. look for content-prefix.media.datasink.protocol.content-type.Handler 
     * <li>If a <code>MediaHandler</code> is found and it is a
     * DataSink, attach the datasource  
     * to it by calling <code>MediaHandler.setSource</code>.<BR>
     * <li>Return the <code>DataSink</code> if there are no errors.
     *
     * <li>If no <code>MediaHandler</code> is found, or if there are
     * any errors,try the next  <code>MediaHandler</code in the list.
     * </ol>
     * <li>If no <code>DataSink</code> is found after trying all of the handlers,
     * a <CODE>NoDataSinkException</CODE> is thrown.
     * </ol>
     * </ol>
     * @param datasource The input <CODE>DataSource</CODE> to the DataSink
     * @param destLocator A <CODE>MediaLocator</CODE> that describes
     * the destination of the media to be handled by the datasink
     * 
     * @return A <CODE>DataSink</CODE> for the media described by the
     * destLocator and that supports the datasource .
     * @exception NoDataSinkException Thrown if no <CODE>DataSink</CODE> can
     * be found.
     * @see NoDataSinkException
     * 
     */
    static public DataSink createDataSink(DataSource datasource,
					  MediaLocator destLocator)
	throws NoDataSinkException {

	String handlerName = "media.datasink." + destLocator.getProtocol() +
	                     ".Handler";

	Vector classList = buildClassList(getContentPrefixList(), handlerName);

	Enumeration handlerList = classList.elements();

	DataSink dataSink = null;
	boolean done = false;
	while (!done && handlerList.hasMoreElements()) {
	    String handlerClassName = (String)handlerList.nextElement();
	    try {
		
		// ... try and instance the handler ...
		Class handlerClass = getClassForName(handlerClassName);
		Object object =  handlerClass.newInstance();
		// ... set the DataSource on it ...
		if (object instanceof DataSink) {
		    dataSink = (DataSink) object;
		    dataSink.setSource(datasource);
		    dataSink.setOutputLocator(destLocator);
		    done = true;
		    break; // we are done
		}


		// Otherwise it must be a DataSinkProxy
		// Get a new data source, and content type  ...
		DataSinkProxy dsProxy = (DataSinkProxy) object;
		String contentType = dsProxy.getContentType(destLocator);
		// .. recurse to try and create a Player with it.

		handlerName = "media.datasink." + destLocator.getProtocol() +
		    "." +
		    contentType +
		    ".Handler";

		Vector dataSinkList = buildClassList(getContentPrefixList(), handlerName);
		Enumeration elements = dataSinkList.elements();
		
		while (elements.hasMoreElements()) {
		    String dsClassName = (String) elements.nextElement();
		    try {
			dataSink = (DataSink) getClassForName(dsClassName).newInstance();
			// ... set the DataSource on it ...
			dataSink.setSource(datasource);
			dataSink.setOutputLocator(destLocator);
			done = true;
			break; // we are done
		    } catch (Exception e) {
			dataSink = null;
		    }
		}
		
	    } catch (Exception e) {
		dataSink = null;
	    } catch (Error e) {
		dataSink = null;
	    }
	}
	if ( dataSink == null )
	    throw new NoDataSinkException("Cannot find a DataSink for: " + datasource);

	Log.comment("DataSink created: " + dataSink);
	Log.comment("  using DataSource: " + datasource + "\n");

	return dataSink;
    }

    /**
     * Retrieve the directory that's used for playback caching.
     * @return the directory that's used for playback caching,
     * 		null if the directory is not specified.
     */
    public static String getCacheDirectory() {
	String cacheDir;
	Object cdir = com.sun.media.util.Registry.get("secure.cacheDir");

	if ( (cdir != null) && (cdir instanceof String) ) {
	    cacheDir = (String) cdir;;

	    if (cacheDir.indexOf(fileSeparator) == -1) {
		if (fileSeparator.equals("/")) {
		    cacheDir = "/tmp";
		} else if (fileSeparator.equals("\\")) {
		    cacheDir = "C:" + fileSeparator + "temp";
		} else {
		    cacheDir = null;
		}
	    }
	    return cacheDir;
	}

	if (fileSeparator.equals("/")) {
	    cacheDir = "/tmp";
	} else if (fileSeparator.equals("\\")) {
	    cacheDir = "C:" + fileSeparator + "temp";
	} else {
	    cacheDir = null;
	}
	return cacheDir;
    }

    /**
     * Specify a hint for the <code>Manager</code> to use.
     * @param hint The name of the hint to be set.
     * @param value The value the hint is to be set. 
     * @see #MAX_SECURITY
     * @see #CACHING
     * @see #LIGHTWEIGHT_RENDERER
     * @see #PLUGIN_PLAYER
     */
    public static void setHint(int hint, Object value) {
	if ( (value != null) &&
	     (hint >= 1) &&
	     (hint <= numberOfHints) ) { // if test not really necessary
	    hintTable.put(new Integer(hint), value);
	}
    }

    /**
     * Retrieve the value of a hint set.
     * @param hint The name of the hint.
     * @return The value of the hint.
     * @see #MAX_SECURITY
     * @see #CACHING
     * @see #LIGHTWEIGHT_RENDERER
     * @see #PLUGIN_PLAYER
     */
    public static Object getHint(int hint) {
	if ( (hint >= 1) && (hint <= numberOfHints) ) {
	    return hintTable.get(new Integer(hint));
	} else {
	    return null;
	}
    }

    static final int DONE = 0;
    static final int SUCCESS = 1;

    /**
     * Realize a player or processor.  It blocks until the player is
     * realized or if the realize fails.
     * Throws a CannotRealizeException if the realize fails.
     */
    static private void blockingCall(Player p, int state) throws CannotRealizeException {

	// Use this sort of as a pass-by-reference variable.
	boolean sync[] = new boolean[2];
	ControllerListener cl;

	sync[DONE] = false;
	sync[SUCCESS] = false;
	cl = new MCA(sync, state);

	p.addControllerListener(cl);
	if (state == Controller.Realized)
	    p.realize();
	else if (state == Processor.Configured)
	    ((Processor)p).configure();

	// Wait for notification from the controller.
	synchronized (sync) {
	    while (!sync[DONE]) {
		try {
		    sync.wait();
		} catch (InterruptedException e) {}
	    }
	}

	p.removeControllerListener(cl);
	if (!sync[SUCCESS])
	    throw new CannotRealizeException();
    }

    
    /**
     * Build a list of <CODE>DataSource</CODE> class names from the
     * protocol prefix-list and a protocol name.
     * <p>
     * The first name in the list will always be:
     * <blockquote><pre>
     * media.protocol.<protocol>.DataSource
     * </pre></blockquote>
     * <p>
     *
     * Each additional name looks like:
     * <blockquote><pre>
     * <protocol-prefix>.media.protocol.<protocol>.DataSource
     * </pre></blockquote>
     * for every <CODE><protocol-prefix></CODE> in the
     * protocol-prefix-list.
     *
     * @param protocol The name of the protocol the source must
     * support.
     * @return A vector of strings, where each string is
     * a <CODE>Player</CODE> class-name.
     */
    static public Vector getDataSourceList(String protocolName) {

	// The first element is the name of the protocol handler ...
	String sourceName =
	    "media.protocol." + protocolName + ".DataSource";

	return buildClassList(getProtocolPrefixList(), sourceName);
    }

    /**
     * Build a list of Player <CODE>Handler</CODE> classes from the
     * content-prefix-list and a content name.
     * <p>
     * The first name in the list will always be:
     * <blockquote><pre>
     * media.content.<contentType>.Handler
     * </pre></blockquote>
     * <p>
     *
     * Each additional name looks like:
     * <blockquote><pre>
     * <content-prefix>.media.content.<contentName>.Player
     * </pre></blockquote>
     * for every <CODE><content-prefix></CODE> in the
     * content-prefix-list.
     *
     * @param contentName The content type to use in the class name.
     * @return A vector of strings where each one is a <CODE>Player</CODE>
     * class-name.
     */
    static public Vector getHandlerClassList(String contentName) {

	// players are found by content type ....
	String handlerName = "media.content." + 
			ContentDescriptor.mimeTypeToPackageName(contentName) + 
			".Handler";

	// ... build a list of classes using the content-prefix-list.
	return buildClassList(getContentPrefixList(), handlerName);
    }

    /**
     * Build a list of Processor <CODE>Handler</CODE> classes from the
     * content-prefix-list and a content name.
     * <p>
     * The first name in the list will always be:
     * <blockquote><pre>
     * media.processor.<contentType>.Handler
     * </pre></blockquote>
     * <p>
     *
     * Each additional name looks like:
     * <blockquote><pre>
     * <content-prefix>.media.processor.<contentName>.Processor
     * </pre></blockquote>
     * for every <CODE><content-prefix></CODE> in the
     * content-prefix-list.
     *
     * @param contentName The content type to use in the class name.
     * @return A vector of strings where each one is a <CODE>Processor</CODE>
     * class-name.
     */
    static public Vector getProcessorClassList(String contentName) {

	// players are found by content type ....
	String handlerName = "media.processor." + 
			ContentDescriptor.mimeTypeToPackageName(contentName) + 
			".Handler";

	// ... build a list of classes using the content-prefix-list.
	return buildClassList(getContentPrefixList(), handlerName);
    }

    /**
     * Build a list of complete class names.
     *<p>
     * 
     * For each element of the prefix-list
     * the following element is added to the list:
     * <blockquote><pre>
     *    <prefix>.<name>
     * </pre></blockquote>
     * These are added to the list in the same order as the prefixes appear
     * in the prefix-list.
     * </ol>
     * 
     * @param prefixList The list of prefixes to prepend to the class name.
     * @param name The name of the class to build the list for.
     * @return A vector of class name strings.
     */
    static Vector buildClassList(Vector prefixList, String name) {
	
	// New list which has the name as the first element ...
	Vector classList = new Vector();

	// Try and instance one directly from the classpath
	// if it's there.
	// $jdr: This has been objected to as confusing,
	// the argument for it's inclusion is that it
	// gives the user (via the classpath) a way
	// of modifying the search list at run time
	// for all applications.
	classList.addElement(name);

	// ... for each prefix append the name and put it
	// in the class list ...
	Enumeration prefix = prefixList.elements();
	while( prefix.hasMoreElements()) {
	    String prefixName = (String)prefix.nextElement();
	    classList.addElement(prefixName + "." + name);
	}

	// ... done
	return classList;
    }
    

    static Vector getContentPrefixList() {
	return (Vector)PackageManager.getContentPrefixList().clone();
    }

    static Vector getProtocolPrefixList() {
	return (Vector) PackageManager.getProtocolPrefixList().clone();
    }

    private static int getNTypesOfCaptureDevices() {
	int nDevices = 0;
	Vector audioDevs = CaptureDeviceManager.getDeviceList(new AudioFormat(null));
	Vector videoDevs = CaptureDeviceManager.getDeviceList(new VideoFormat(null));
	if (audioDevs != null && audioDevs.size() > 0)
	    nDevices++;
	if (videoDevs != null && videoDevs.size() > 0)
	    nDevices++;
	return nDevices;
    }


    private static boolean checkIfJDK12() {
	if (jdkInit)
	    return (forName3ArgsM != null);
	jdkInit = true;
	try {
	    forName3ArgsM = Class.class.getMethod("forName",
						  new Class[] {
		String.class, boolean.class, ClassLoader.class
		    });
	    
	    getSystemClassLoaderM = ClassLoader.class.getMethod("getSystemClassLoader", null);

	    // TODO: may need to invoke RuntimePermission("getClassLoader") privilege
	    systemClassLoader = (ClassLoader) getSystemClassLoaderM.invoke(ClassLoader.class, null);

	    getContextClassLoaderM = Thread.class.getMethod("getContextClassLoader", null);

	    return true;
	} catch (Throwable t) {
	    forName3ArgsM = null;
	    return false;
	}
    }

    // This is a Package private class
    static Class getClassForName(String className) 
                throws ClassNotFoundException {
	/**
	 *  Note: if we don't want this functionality
	 *  just replace it with Class.forName(className)
	 */

	try {
	    return Class.forName(className);
	} catch (Exception e) {
	    if (!checkIfJDK12()) {
		throw new ClassNotFoundException(e.getMessage());
	    }
	} catch (Error e) {
	    if (!checkIfJDK12()) {
		throw e;
	    }
	}

	/**
	 *  In jdk1.2 application, when you have jmf.jar in the ext directory and
	 *  you want to access a class that is not in jmf.jar but is in the CLASSPATH,
	 *  you have to load it using the the system class loader.
	 */
	try {
	    return (Class) forName3ArgsM.invoke(Class.class, new Object[] {
		className, new Boolean(true), systemClassLoader});
	} catch (Throwable e) {
	}

	/**
	 *  In jdk1.2 applet, when you have jmf.jar in the ext directory and
	 *  you want to access a class that is not in jmf.jar but applet codebase,
	 *  you have to load it using the the context class loader.
	 */
	try {
	    // TODO: may need to invoke RuntimePermission("getClassLoader") privilege
	    ClassLoader contextClassLoader =
		(ClassLoader) getContextClassLoaderM.invoke(Thread.currentThread(), null);
	    return (Class) forName3ArgsM.invoke(Class.class, new Object[] {
		className, new Boolean(true), contextClassLoader});
	} catch (Exception e) {
	    throw new ClassNotFoundException(e.getMessage());
	} catch (Error e) {
	    throw e;
	}
    }

    
}


/**
 * A utility class as a ControllerLister to implement the blocking realize.
 */
class MCA extends ControllerAdapter {

    // Pass-by-reference for this class.
    boolean sync[];
    int     state;
    
    public MCA(boolean sync[], int state) {
	this.sync = sync;
	this.state = state;
    }

    private void succeed() {
	synchronized (sync) {
	    sync[Manager.DONE] = true;
	    sync[Manager.SUCCESS] = true;
	    sync.notify();
	}
    }

    private void fail() {
	synchronized (sync) {
	    sync[Manager.DONE] = true;
	    sync[Manager.SUCCESS] = false;
	    sync.notify();
	}
    }

    public void configureComplete(ConfigureCompleteEvent evt) {
	if (state == Processor.Configured)
	    succeed();
    }
    
    public void realizeComplete(RealizeCompleteEvent evt) {
	if (state == Controller.Realized)
	    succeed();
    }

    public void controllerError(ControllerErrorEvent evt) {
	fail();
    }

    public void deallocate(DeallocateEvent evt) {
	fail();
    }

    public void controllerClosed(ControllerClosedEvent evt) {
	fail();
    }
}