/*
 * @(#)MediaPlayer.java	1.22 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

//  @version 1.0
//  Last Updated: 02/19/99
//  Notes: N/A


package javax.media.bean.playerbean;

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.lang.String;
import java.lang.reflect.*;
import java.net.*;
import java.io.*;
import java.util.Properties;
import java.util.Vector;
import javax.media.*;
import javax.media.protocol.*;
import java.beans.*;
import javax.media.rtp.*;
import javax.media.format.*;
import javax.media.rtp.event.*;
import java.applet.AppletContext;


/**
  *   <CODE>MediaPlayer</CODE> encapsulates a player in a 
  *   JavaBeans<sup><font size="-2">TM</sup></font> component. 
  *   <CODE>MediaPlayer</CODE> implements the <CODE>Player</CODE> and <CODE>Externalizable</CODE> 
  *   interfaces. 
  *  <p>
  *   A <CODE>MediaPlayer</CODE> can be used as a 
  *   full-featured <CODE>Player</CODE> with either the default <CODE>Player</CODE> controls or
  *   custom controls. 
  *
  *  <h3><CODE>MediaPlayer</CODE> Properties</h3>
  *   <p>
  *   The following bean properties are supported by <CODE>MediaPlayer</CODE>:
  *   <ul>
  *   <li>loop: Indicates whether or not the media will be played repeatedly. 
  *   
  *   <li>media location: The location of the media to be played. The location can be either an
  *       absolute address such as <CODE>http://xena/demo/media/Sample1.mov</CODE>
  *       or an address relative to the codebase address such as <CODE>media/Sample1.mov</CODE>
  *   <li>name: The name of this <CODE>MediaPlayer</CODE>.
  *   <li>size and position: The size and position of this 
  *       <CODE>MediaPlayer</CODE>.
  *   <li>volume: The audio volume as a number from 0 to 5, where  0 is muted 
  *   and 5 is the loudest possible volume. The default value is 3.
  *       
  *   <li>show control panel: Indicates whether or not the video control panel is 
  *       displayed.
  *   <li>show caching control: Indicates whether or not the caching control is displayed.
  *       The caching control shows the download progress. 
  *         
  *   <li>fixed aspect ratio: Indicates whether or not the aspect ratio of the video  
  *       is maintained when the visual component is resized.
  *   </ul>
  * <p>
  * Two additional properties can be set. These are not bean properties, but provide
  * additional control over the presentation: 
  * <ul>
  * <li>ZoomLevel: Can be set through the popup menu and via set and get methods.  
  *   (Click the right mouse button over the media to bring up the popup menu.)
  *   
  * <li>MediaLocationVisible: Can be set through get and set methods. The media location 
  * is not editable during runtime.
  * 
  * <h3>Invoking <CODE>MediaPlayer</CODE></h3>
  * <p>  
  * You can instantiate MediaPlayer directly or using a bean-enabled IDE tool such as NetObjects
  * BeanBuilder. To instantiate <CODE>MediaPlayer</CODE> directly:
  * 
  * <pre>
  *	      ClassLoader cl = this.getClass().getClassLoader();
  *	      MediaPlayer pb = (MediaPlayer)Beans.instantiate(cl,
  *		   			     "javax.media.bean.playerbean.MediaPlayer");
  * </pre>   
  *  
  */

public class MediaPlayer extends Container
implements Player,Externalizable {

    private PropertyChangeSupport changes = new PropertyChangeSupport(this);

    // the media location the user specified.  It can be absolute or relative
    // address.
    private String urlString = "";
    private MediaLocator mrl;
    private URL  url; 
    private AppletContext mpAppletContext = null;  //applet context of container
  
    //Should the Player's control panel be visible?
    private boolean panelVisible = true;

    //Should the Player's caching control panel be visible?
    private boolean cachingVisible = false;
  
    //Should maintain aspect ratio?
    private boolean fixedAspectRatio = true;
  
    //Should the user be able to scale the video?
    private boolean fitVideo = true;
  
    //Should the video loop after it has reached the end?
    private boolean looping = true;

    //Private variables:

    //The actual JMF Player
    transient Player player = null;

    //The panel which contains the player components: [visual component,
    // control panel]
    transient Panel panel;

    // the panel which contains the visual component of a player.
    transient Panel vPanel;
  
    //the panel for control component and url
    transient Panel newPanel;

    //And the visual component itself
    transient Component visualComponent;

    //and the controlComponent
    transient Component controlComponent;
  
    //and the cachingControl progress bar component
    transient Component cachingComponent;

    //How high is the controlPanel
    private transient int controlPanelHeight = 0;
  
    //How high is the urlField
    private transient int urlFieldHeight = 0;
  
    //The preferredHeight
    private int preferredHeight;

    //And PreferredWidth
    private int preferredWidth;

    //And, of course, the current state of the player
    private int state;

    // a set of MediaPlayers, which are placed under this bean's control
    private Vector controlListeners = new Vector();

    //popup menu for controlling the size
    PopupMenu zoomMenu = null;
  
    //code base of Applet
    private URL mpCodeBase = null;

    protected transient GainControl gainControl;

    // external  for the current volume level
    protected transient String curVolumeLevel = 
    MediaPlayerResource.getString("THREE");
    // internal presentation for the current volume level
    protected transient float curVolumeValue = 0.6f;

    // external presentation for the current zoom level
    protected transient String curZoomLevel = 
    MediaPlayerResource.getString("SCALE_NORMAL");
    // internal presentation for the current zoom level
    protected transient float curZoomValue = 1.0f;

    transient protected Time  mediaTime;

    private InternalControllerAdapter selfListener;
  
    //media length for caching control  
    private long contentLength=0;
  
    private boolean fixtedFirstTime=true;
  
    private boolean displayURL = false;
  
    //if popup menu is active 
    private boolean isPopupActive = true;
  
    //The widget displaying the media location for the player
    private transient TextField urlName;
  
    private transient visualMouseAdapter mouseListen;
  
    /**
     *
     * InternalControllerAdapter:
     *   Inner class to catch player event
     *
     */
    class InternalControllerAdapter  extends ControllerAdapter {
	private MediaPlayer thisBean;
	/**
	 *
	 * Constructs an <CODE>InternalControllerAdapter</CODE>.
	 * @param b  Instance of <CODE>MediaPlayer</CODE> bean  
	 *
	 */
	public  InternalControllerAdapter(MediaPlayer b) {
	    super();
	    thisBean=b;
	}
    
	/**
	 *
	 * Replaces the URL that is invoked when a hot 
	 *                 link to a new URL is triggered.
	 *
	 * @param ReplaceURLEvent   A <CODE>ControllerEvent</CODE> that indicates that the  
	 *                          mediaLocation has been changed. 
	 *
	 */
    
	public void replaceURL(Object e) { 
	    debug("ReplaceURLEvent ");
	    URL  retobj=null;
	    try {
		Class cls = Class.forName("com.ibm.media.ReplaceURLEvent");  
		Method meth = cls.getMethod("getURL",(Class[])null);
		retobj = (URL)meth.invoke(e,(Object[]) null);
	    } catch (Throwable t) {
		System.err.println(t);
	    }
	    setMediaLocation(retobj.toExternalForm());
	    thisBean.start(); 
	}
	 
	/**
	 *
	 * Displays the html document which is invoked when a 
	 *                   hotlink to a html is triggered in a mvr file
	 *
	 * @param   ShowDocumentEvent a <CODE>ControllerEvent</CODE> that indicates that   
	 *           URL to show in a web page.               
	 *
	 */
    
	public void showDocument(Object e) { 
	    debug("ShowDocumentEvent ");
	    initSetCodeBase();
	    URL  retURL=null;
	    String retContext=null;
	    try {
		Class cls = Class.forName("com.ibm.media.ShowDocumentEvent");  
		Method meth = cls.getMethod("getURL",(Class[])null);
		retURL = (URL)meth.invoke(e, (Object[])null);
		meth = cls.getMethod("getString",(Class[])null);
		retContext = (String)meth.invoke(e, (Object[])null);
	    } catch (Throwable t) {
		System.err.println(t);
	    }
	    if (mpAppletContext !=null) {
		mpAppletContext.showDocument(retURL,retContext);
	    }
	}
    
	/**
	 *
	 * Gets the current state.
	 *
	 * @param TransitionEvent   A <CODE>ControllerEvent</CODE> that indicates that a 
	 *                          <CODE>Controller</CODE> has changed state. 
	 *
	 */
	public void transition(TransitionEvent e) { 
	    debug("TransitionEvent "+e);
	    state = e.getCurrentState();
	}
	  
	/**
	 *
	 * When the player is Realized, it calls doRealize
	 *    internal function to get the visual, gain, control components and
	 *    add them to the panel of bean. If fixedAspectRatio is set, it will 
	 *    check if Bean has more space to expand the visual component and calculate
	 *    the  
	 *
	 * @param RealizeCompleteEvent   A <CODE>ControllerEvent</CODE> that indicates a 
	 *                               <CODE>Controller</CODE> has finished realizing. 
	 */
	   
	public void realizeComplete(RealizeCompleteEvent e) {
	    debug("  Event:  RealizeComplete");
	    doRealize();	  
	}
	   
	/**
	 *
	 * When the player is Prefetched, removes the progress bar,
	 *          sets the size, and sets the visual component visible
	 *   
	 * @param prefetchCompleteEvent  A <CODE>ControllerEvent</CODE> that indicates a
	 *                               <CODE>Controller</CODE> has finished Prefetching. 
	 */ 
	public void prefetchComplete(PrefetchCompleteEvent e) {
	    debug("  Event:prefetchComplete");
	    //doPrefetch();
	   
	}
	/**
	 *
	 * When player format changes, remove the old visual component  
	 *          then create the new one.
	 *   
	 * @param FormatChangeEvent  A <CODE>ControllerEvent</CODE> that indicates a
	 *                               <CODE>Controller</CODE> changes format. 
	 */  
	public void formatChange(FormatChangeEvent e) {
	    debug(" formatChangeevent");
	    Component oldVisualComp = visualComponent;
	    if (player==null) {
		Fatal(MediaPlayerResource.getString("SHOULD NOT OCCUR"));
		System.exit(-1);
	    }
	    if (( visualComponent = player.getVisualComponent())!= null) {      
		if (oldVisualComp !=visualComponent) {
		    if (oldVisualComp !=null) {
			oldVisualComp.remove(zoomMenu);
			vPanel.remove(oldVisualComp);
			panel.remove(vPanel);
			Component oldComp = controlComponent;
			if ((oldComp != null) && (newPanel != null)) {
			    newPanel.remove(oldComp);
			    if (displayURL == true)
				newPanel.remove(urlName);
			    panel.remove(newPanel);
			}
		    }
		    doRealize();
		} else if ((getMediaLocation().endsWith("mvr"))||
			   (getMediaLocation().endsWith("MVR"))) {
		    //     calculateSize();
		    zoomTo((float)1.0);          //mvr doesn't zoom so not calculation needed
		    //     zoomTo(curZoomValue);
		}
	    } else {
		Component oldComp = controlComponent;
		if ((( controlComponent = player.getControlPanelComponent()) != null) && 
		    (isControlPanelVisible())) {
		    if (  oldComp != controlComponent) {
			if (oldComp!=null) {
			    newPanel.remove(oldComp);
			    if (displayURL==true)
				newPanel.remove(urlName);
			    panel.remove(newPanel);
			}
			doRealize();
		    }
		} else {    //new control comp is null   
		    if ((oldVisualComp !=null)&&(vPanel!=null)) {
			oldVisualComp.remove(zoomMenu);
			vPanel.remove(oldVisualComp);
			panel.remove(vPanel);
		    }
		}
	    }
      
	}
  	   
	/**
	 *
	 * When the player is started.
	 *
	 * @param    StartEvent   A <CODE>ControllerEvent</CODE> that indicates a <CODE>Controller</CODE>
	 *                               has entered into the <EM>Started</EM> state
	 */ 
	public void start(StartEvent e) {
	    debug("  Event:StartEvent ");   
	}
      
	/**
	 *
	 * When the player reach the end of media, if loop is set, 
	 *    restart it.
	 * @param     EndOfMediaEvent  A <CODE>ControllerEvent</CODE> that indicates a <CODE>Controller</CODE>
	 * has reached the end of its media and is stopping. 
	 *
	 */ 
	public void endOfMedia(EndOfMediaEvent e) {
	    // We've reached the end of the media; rewind and
	    // start over
	    debug("  Event:EndofMediaEvent");
	    if (isPlayBackLoop()) {
		synchronized (this) {
		    if (player==null) {
			Fatal(MediaPlayerResource.getString("SHOULD NOT OCCUR"));
			System.exit(-1);
		    }    
		    if ( player != null )
			player.setMediaTime(new Time(0));
		    if ( player != null )
			player.start();
		}
	    }
	}
      
	/**
	 *
	 * When the player has an error condition occur, log the 
	 *    error message.
	 * @param     controllerErrorEvent  A <CODE>ControllerEvent</CODE> that indicates a 
	 * <CODE>Controller</CODE> error condition occurs that will cause 
	 * a <CODE>Controller</CODE> to cease functioning.
	 *
	 */
	public void controllerError(ControllerErrorEvent e) {
	    // Tell TypicalPlayerApplet.start() to call it a day
	    debug("  Event:ControllerErrorEvent");
	    player = null;
	    Fatal(e.getMessage());
	}
      
	/**
	 *
	 * When the input video has changed in size, recalculate, 
	 *               and redraw.
	 * @param     SizeChangeEvent  A <CODE>ControllerEvent</CODE> that indicates the
	 *            input video has changed in size and the video renderer needs
	 *            to be resized to specified size
	 *
	 */  
	public void sizeChange(SizeChangeEvent e) {
	    debug("  Event:SizeChangeEvent");
	    int newPanelHeight =0;
	    if ((controlComponent != null) && isControlPanelVisible())
		newPanelHeight= controlPanelHeight;
	
	    if ((urlName!=null) &&(isMediaLocationVisible()))
		newPanelHeight += urlFieldHeight;
	    if (visualComponent != null){
		visualComponent.setSize(e.getWidth(), e.getHeight());
		preferredHeight = visualComponent.getPreferredSize().height; 
		preferredWidth = visualComponent.getPreferredSize().width; 
	    }
	    if (panel != null) {
		if (vPanel != null) {
		    vPanel.setBounds(0, 0, e.getWidth(),
				     e.getHeight()-newPanelHeight );
		    vPanel.validate();
		}  
		panel.setBounds(0, 0, e.getWidth(), e.getHeight());
		panel.validate();
	    }
	    invalidate();
	    validate();
	}
    
	/**
	 *
	 * When the caching event generated, if no progress
	 *               bar, then create it, if download completed, remove 
	 *               the progress bar.
	 * @param     cachingControlEvent  A <CODE>ControllerEvent</CODE> that indicates the
	 *            caching state change.
	 *
	 */    
	public void cachingControl(CachingControlEvent e) {
	    debug("  Event:  CachingControl");
	    if (isCachingControlVisible() == true) {
		CachingControl cc = e.getCachingControl();
		long contentLength=cc.getContentLength();
		if ((cc != null && cachingComponent == null)&& 
		    (contentLength!=CachingControl.LENGTH_UNKNOWN))  {
		    cachingComponent = cc.getControlComponent();
		    if (cachingComponent == null)
			cachingComponent = cc.getProgressBarComponent();
		
		    if (cachingComponent != null) {
			panel.add("South",cachingComponent);
			Dimension prefSize = cachingComponent.getPreferredSize();
			cachingComponent.setBounds(0, 0, prefSize.width, prefSize.height);
			panel.setSize(prefSize.width, prefSize.height);
			panel.validate();
		    }
		} else {
		    if (cc.getContentProgress()==contentLength){
			if (cachingComponent!=null) {
			    panel.remove(cachingComponent);
			    validate();
			}
		    }
		}
	    }
	}
      
    }
 
    /**
     *   Constructs a MediaPlayer with its main panel preparing for 
     *   caching control events and the following visual display.
     *
     */
    public MediaPlayer() {
      
	super();
	setLayout(null);
	if (panel == null) {
	    panel = new Panel();
	    panel.setLayout(new BorderLayout());
	    panel.setVisible(false);
	    add("Center",panel);
	}
	   
    }

    /**
     *
     * doRealize(): internal function to get the visual, gain, control components and
     *    add them to the panel of bean. If fixedAspectRatio is set, it will 
     *    check if Bean has more space to expand the visual component and calculate
     *    the zoom factor.  
     *
     */
    private synchronized void doRealize() {
	debug("in doRealize");
	if (player==null) {
	    Fatal(MediaPlayerResource.getString("SHOULD NOT OCCUR"));
	    System.exit(-1);
	}
	gainControl = player.getGainControl();
	    
	if (gainControl !=null) {
	    float re=gainControl.setLevel(curVolumeValue);  
	}
	      
	if (( visualComponent = player.getVisualComponent())!= null) {
	    vPanel = new Panel();
	    vPanel.setLayout( new BorderLayout() );
	    panel.setVisible(false);
	    visualComponent.setVisible(false);
	    vPanel.add("Center", visualComponent);
	    panel.add("Center", vPanel);
	  
	    addComponentListener(new visualComponentAdapter(this));
	    addPopupMenu(visualComponent);
	    setPopupActive();
	}
      
	newPanel=new Panel();
	newPanel.setLayout(new BorderLayout());
	panel.add("South",newPanel);
	urlName = new TextField(10);
	urlName.setEditable(false);
      
	if (displayURL==true) {
	    urlName.setText(urlString);
	    newPanel.add("South",urlName);
	}
	        
	if ((( controlComponent = player.getControlPanelComponent()) != null)&& 
	    (isControlPanelVisible())) {

	    controlComponent.setVisible(false);
	    newPanel.add("North",controlComponent);
	}
		    
	calculateSize();
      
	if (visualComponent == null) {
	    if (controlComponent !=null)
		panel.setSize(controlComponent.getPreferredSize().width,
			      controlPanelHeight+urlFieldHeight );     
	    else
		panel.setSize(100,urlFieldHeight);
	  
	}	
	showVisual();
    }
    
    private  void calculateSize() {
        if ( player == null )
            return;
	debug("claculateSize");
	if (( visualComponent = player.getVisualComponent())!= null) {
	    //   visualComponent.setSize(visualComponent.getPreferredSize());    
	    preferredHeight =visualComponent.getPreferredSize().height; 
	    preferredWidth =visualComponent.getPreferredSize().width; 
	    
	}
	
	if (((( controlComponent = player.getControlPanelComponent()) != null)&& 
	     (isControlPanelVisible()))||(displayURL==true)) {
	    if ((controlComponent!=null)&& (isControlPanelVisible())) {
		controlPanelHeight = controlComponent.getPreferredSize().height;
		preferredHeight +=controlPanelHeight;
		if (preferredWidth ==0)
		    preferredWidth = 320;
		
	    }
	    if (displayURL==true) {
		urlFieldHeight = urlName.getPreferredSize().height;
		preferredHeight +=urlFieldHeight;
	    }
	    
	}
	
	if (visualComponent != null) {
	    Dimension vSize = visualComponent.getPreferredSize();
	    int totalHeight=0;
	    if (controlComponent != null) {
		totalHeight+=controlComponent.getPreferredSize().height;
	    }
	    if (displayURL==true) {
		totalHeight +=urlName.getPreferredSize().height;
	    }
	    
	    if ((  fixedAspectRatio==true) && (curZoomValue == 1.0)) {
		
		//compare the ratio of parent and panel, and get the smaller number
		
		if ((vSize.width!=0) &&(vSize.height!=0)) {
		    if (((float)getSize().width/(float)vSize.width) >=
			((float)(getSize().height-totalHeight)/
			 (float)(vSize.height))) {
			
			curZoomValue =  
			    ((float)(getSize().height-totalHeight)/
			     (float)(vSize.height));
		    } else {    
			curZoomValue = 
			    ((float)getSize().width/vSize.width);
		    }
		}
		if (curZoomValue <0.5)
		    curZoomValue=1;
		
	    }	
	    if (newPanel !=null)
                newPanel.setSize(visualComponent.getPreferredSize().width,
				 controlPanelHeight+urlFieldHeight);
	    
	} 	 
	
    }

    
    private synchronized void showVisual() {
	//debug(" doPrefetch");
	if (cachingComponent!=null) {
	    panel.remove(cachingComponent);
	    validate();
	}
	
	if ((visualComponent!=null) && 
	    ((fixedAspectRatio==true) || (fixtedFirstTime==false))) {
	    zoomTo(curZoomValue);
	} else {
	    panel.setSize(getSize());
	}
	panel.setVisible(true);
	if (visualComponent != null) {
	    visualComponent.setVisible(true);  
	}
	if ((controlComponent != null)&& (isControlPanelVisible())) {
	    controlComponent.setVisible(true); 
	}
	
	panel.validate();
	validate(); 
    }
    
    /**
     * initSetCodeBase():
     *   set codebase for relative media location.
     *
     */
    private void initSetCodeBase() {
	if (mpCodeBase!=null)
	    return;
	
	Container p = getParent();
	while (p!=null) {
	    if (p instanceof Applet) 
		break;
	    p = p.getParent();
	}
	if (p!=null) {
	    setCodeBase(((Applet)p).getCodeBase());
	    mpAppletContext = ((java.applet.Applet) p).getAppletContext();
	}
	
    }
    
    
    // MediaPlayer property methods:
    
    /**
     * Gets the value of the media location property for this <CODE>MediaPlayer</CODE>.
     *
     * @return  A <CODE>String</CODE> that contains the media location.
     *
     */
    public String getMediaLocation() {
	if (mrl !=null) {
	    return mrl.toString();
	} else {
	    return urlString;
	}
    }

    /**
     *
     * Gets a <CODE>MediaLocator</CODE> for this <CODE>MediaPlayer</CODE>
     * that corresponds to the specified <CODE>String</CODE>.
     *
     * @param filename A <CODE>String</CODE> that contains the media name 
     * from which to create the <CODE>MediaLocator</CODE>.
     * @return A <CODE>MediaLocator</CODE> that corresponds to the specified <CODE>String</CODE>.
     *
     */
    protected MediaLocator getMediaLocator(String filename) {
	MediaLocator localml = null;
	
	if (filename.regionMatches(true, 0, "<codebase>", 0, 10)) {
	    try {
		if (mpCodeBase == null)
		    initSetCodeBase();
		localml = new MediaLocator( new URL(mpCodeBase, filename.substring(11)));
	    } catch (java.net.MalformedURLException e) {
		if (mpCodeBase != null ) {
		    log(MediaPlayerResource.getString("NO IMAGE:BAD_URL")+
			filename+" " + mpCodeBase);
		    urlString = " ";
		}
		return null;
	    }  
	} else {
	    localml = new MediaLocator(filename);
	}
   
	return localml;
    }
 
    /**
     *   Sets the media location property for this <CODE>MediaPlayer</CODE>.
     *   This property specifies the location of the media file to be 
     *   presented by the <CODE>MediaPlayer</CODE>.
     *   If a <CODE>Player</CODE> already exists for this <CODE>MediaPlayer</CODE>, 
     *   this method stops that <CODE>Player</CODE> and releases all
     *   of the resources it is using.  Then a new <CODE>Player</CODE> is created to
     *   present the specified media file.
     *   All controller listeners  registered  for the old
     *   <CODE>Player</CODE> are added to the listener list of the new <CODE>Player</CODE>.
     *
     * @param location A <CODE>String</CODE> that contains the location of the media file.
     *
     */
    public void setMediaLocation(String location) {
   
	try {
	    String old = "";
	    if (mrl !=null) {
		old = mrl.toExternalForm();
		if (panel != null)
		    panel.removeAll();
		if (player != null) {
		    player.stop();
		    player.close();
		    panel.validate();
		    if (controlListeners.contains(selfListener))
		        controlListeners.removeElement(selfListener);
		}
	    }
	    urlString = location;
	    if ((mrl = getMediaLocator(location))==null) {
		//urlString = "";
		return;
	    }
	    try {
		player = javax.media.Manager.createPlayer(mrl);
	    } catch (Exception e) {
		player = null;
		urlString = " ";
		changes.firePropertyChange("mediaLocation",  location, old);
		Fatal(MediaPlayerResource.getString("UNABLE_CREATE_PLAYER")+e);
		return;
	    }
	   	    
	    if (player==null) {
		return;
	    }
      
	    if (urlName !=null) {
		urlName.setText(urlString);
		urlName.setFont(getFont());
	    }
	    player.addControllerListener(selfListener=new InternalControllerAdapter(this));
      
	    if (!controlListeners.isEmpty()) {   
		for (int i=0;i<controlListeners.size();i++)
		    player.addControllerListener(
						 (ControllerListener)controlListeners.elementAt(i));
	    }
	    changes.firePropertyChange("mediaLocation", old, location);
      
	} catch (Exception e) {
	    mrl = null;
	    e.printStackTrace();
	    Fatal(MediaPlayerResource.getString("UNABLE_CREATE_PLAYER")+e);
	    return;
	}
    }

    /**
     *   Sets the <CODE>MediaLocator</CODE> for this <CODE>MediaPlayer</CODE>. This  
     *   method creates a <CODE>Player</CODE> for the specified <CODE>MediaLocator</CODE>. 
     *   If a <CODE>Player</CODE> already exists for this <CODE>MediaPlayer</CODE>, 
     *   this method stops that <CODE>Player</CODE> and releases all
     *   of the resources it is using.  Then a new <CODE>Player</CODE> is created with
     *   the specified <CODE>MediaLocator</CODE>.
     *   All controller listeners  registered  for the old
     *   <CODE>Player</CODE> are added to the listener list of the new <CODE>Player</CODE>.
     *
     * @param locator A <CODE>MediaLocator</CODE> that identifies the media file to be 
     * presented by the <CODE>MediaPlayer</CODE>.
     *
     */
    public void setMediaLocator(MediaLocator locator) {
	try {
	    debug("setMediaLocator");
	    if (locator != null) {
		if (mrl !=null) {
		    if (panel != null)
		        panel.removeAll();
		    if (player != null) {
		        player.stop();
		        player.close();
		        if (controlListeners.contains(selfListener))
			    controlListeners.removeElement(selfListener);
		    }
		}
	    } else
		return;
	      
	    try {
		player = javax.media.Manager.createPlayer(locator);
	    } catch (NoPlayerException e) {
		player = null;
		urlString = " ";
		Fatal(MediaPlayerResource.getString("UNABLE_CREATE_PLAYER")+e);
		return;
	    } catch (java.io.IOException e) {
		player = null;
		urlString = " ";
		Fatal(MediaPlayerResource.getString("IO_EXCEPTION")+e);
		return;
	    }
	    
	    if (player==null) {
	        return;
	    }
	    urlString = locator.toExternalForm();
	    mrl = locator;
	    if (urlName !=null) {
		urlName.setText(urlString);
		urlName.setFont(getFont());
	    }
	    player.addControllerListener(selfListener=new InternalControllerAdapter(this));
	    
	    if (!controlListeners.isEmpty()) {
		for (int i=0;i<controlListeners.size();i++)
		    player.addControllerListener(
						 (ControllerListener)controlListeners.elementAt(i));
	    }
	   
	} catch (Exception e) {
	    url = null;
	    e.printStackTrace();
	    Fatal(MediaPlayerResource.getString("UNABLE_CREATE_PLAYER")+e);
	    return;
	}
    }

    /**
     *   Sets the <CODE>DataSource</CODE> for this <CODE>MediaPlayer</CODE>. This  
     *   method creates a <CODE>Player</CODE> for the specified <CODE>DataSource</CODE>. 
     *   If a <CODE>Player</CODE> already exists for this <CODE>MediaPlayer</CODE>, 
     *   this method stops that <CODE>Player</CODE> and releases all
     *   of the resources it is using.  Then a new <CODE>Player</CODE> is created with
     *   the specified <CODE>DataSource</CODE>.
     *   All controller listeners  registered  for the old
     *   <CODE>Player</CODE> are added to the listener list of the new <CODE>Player</CODE>. 
     *
     * @param ds A <CODE>DataSource</CODE> that identifies the media file to be 
     * presented by the <CODE>MediaPlayer</CODE>.
     *
     */
    public void setDataSource(DataSource ds) {
	try {
	    debug("setDataSource");
	    if (ds != null) {
		if (panel != null)
		    panel.removeAll();
		if (player != null) {
		    player.stop();
		    if (controlListeners.contains(selfListener))
		        controlListeners.removeElement(selfListener);
		}
	    }
	    if (urlName !=null) {
		if (ds.getLocator()!=null)
		    urlName.setText(ds.getLocator().toExternalForm());
		else
		    urlName.setText(""); 
		urlName.setFont(getFont());
	    }
	    try {
		player = javax.media.Manager.createPlayer(ds);
	    } catch (Exception e) {
		player = null;
		urlString = " ";
		Fatal(MediaPlayerResource.getString("UNABLE_CREATE_PLAYER")+e);
		return;
	    }
	    if (player==null) {
	        return;
	    }
	   
	    if (ds.getLocator()!=null) 
	        urlString = ds.getLocator().toExternalForm();
	    else
	        urlString = ""; 
	    player.addControllerListener(selfListener=new InternalControllerAdapter(this));
	    
	    if (!controlListeners.isEmpty()) {   
		for (int i=0;i<controlListeners.size();i++)
		    player.addControllerListener(
						 (ControllerListener)controlListeners.elementAt(i));
	    }
	   
	} catch (Exception e) {
	    mrl = null;
	    e.printStackTrace();
	    Fatal(MediaPlayerResource.getString("UNABLE_CREATE_PLAYER")+e);
	    return;
	}
    }

    /**
     *   Specifies an existing <CODE>Player</CODE> to use to present media 
     *   for this <CODE>MediaPlayer</CODE>.
     *   If a <CODE>Player</CODE> already exists for this <CODE>MediaPlayer</CODE>, 
     *   this method stops that <CODE>Player</CODE> and releases all
     *   of the resources it is using.  
     *   All controller listeners  registered  for the old
     *   <CODE>Player</CODE> are added to the listener list of the new <CODE>Player</CODE>. 
     *
     * @param newPlayer The <CODE>Player</CODE> to use to present media data for 
     * this <CODE>MediaPlayer</CODE>.
     *
     */
    public void setPlayer(Player newPlayer) {
	debug("setPlayer");
	    
	if (newPlayer == null)
	    return;
   
	if (newPlayer != null) {
	    if (panel != null)
		panel.removeAll();
	    if (player != null) {
		player.stop();
		if (controlListeners.contains(selfListener))
		    controlListeners.removeElement(selfListener);
	    }
	}
	player=newPlayer;
	urlString = "";
	player.addControllerListener(selfListener=new InternalControllerAdapter(this));
	switch(player.getState()) {
	case (Prefetched):
	    debug("player state prefetched ");  
	    break;
	case (Prefetching):
	    debug("player state prefetching ");  
	    break;
	case (Realized):
	    debug("player state Realized ");  
	    break;
	case (Realizing):
	    debug("player state  Realizing");  
	    break;
	case (Started):
	    debug("player state started ");  
	    break;
	case (Unrealized):
	    debug("player state Unrealized ");      
	    break;
	}
	if ((player.getState()==Realized) ||
	    (player.getState()==Prefetching)) 
	    doRealize();
	 
	if  ((player.getState()==Prefetched)  ||
	     (player.getState()==Started)) {
	    doRealize();
	    //doPrefetch();   
	} 	    
	if (!controlListeners.isEmpty()) {
	    for (int i=0;i<controlListeners.size();i++)
		player.addControllerListener(
					     (ControllerListener)controlListeners.elementAt(i));
	}
	   
    }

    /**
     *
     * Gets the loop property for this <CODE>MediaPlayer</CODE>. This property indicates
     * whether the <CODE>MediaPlayer</CODE> should play the media stream 
     * repeatedly or stop when the end of media is reached.
     *
     * @return <CODE>true</CODE> if loop is enabled, <CODE>false</CODE> if the media stream 
     * is to be played only once.
     *
     */
	  
    public boolean getPlaybackLoop() {
	return looping;
	    
    }

    /**
     *
     * Sets the loop property for this <CODE>MediaPlayer</CODE>.  This property indicates
     * whether the <CODE>MediaPlayer</CODE> should play the media stream 
     * repeatedly or stop when the end of media is reached.
     *
     * @param val  The boolean value for the loop property. Specify <CODE>true</CODE> 
     * to enable looping, <CODE>false</CODE> if the media stream 
     * is to be played only once.
     *
     */
    public void setPlaybackLoop(boolean val) {
	boolean old = looping;
	looping = val;
	changes.firePropertyChange("playbackLoop", new Boolean(old),
				   new Boolean(looping));
    }

    /**
     *
     * Gets the loop property for this <CODE>MediaPlayer</CODE>. This property indicates
     * whether the <CODE>MediaPlayer</CODE> should play the media stream 
     * repeatedly or stop when the end of media is reached.
     *
     * @return <CODE>true</CODE> if loop is enabled, <CODE>false</CODE> if the media stream 
     * is to be played only once.
     *
     */
    public boolean isPlayBackLoop() {
	return looping;
    }

    /**
     *
     * Sets the zoom property for this <CODE>MediaPlayer</CODE>. This property specifies
     * a scale factor for the visual components. When <CODE>zoom</CODE> is called, the 
     * visual components are resized accordingly. 
     *
     * @param scale A <CODE>String</CODE> that specifies the zoom factor. Possible values are
     *     <CODE>Scale 1:2</CODE>, <CODE>Scale 1:1</CODE>, <CODE>Scale 2:1</CODE>, 
     *  and <CODE>Scale 4:1</CODE>.
     *
     */
    public void setZoomTo(String scale) {
	debug("setZoomTo");
	curZoomLevel = scale;
	if (scale.trim().equals(MediaPlayerResource.getString("1:2"))) {
	    curZoomValue = 0.5f;
	} else if (scale.trim().equals(MediaPlayerResource.getString("1:1"))) {
	    curZoomValue = 1.0f;
	} else if (scale.trim().equals(MediaPlayerResource.getString("2:1"))) {
	    curZoomValue = 2.0f;
	} else if (scale.trim().equals(MediaPlayerResource.getString("4:1"))) {
	    curZoomValue = 4.0f;
	}

	fixtedFirstTime=false;
	zoomTo(curZoomValue);
    }  
   
    /**
     *
     * Gets the zoom property for this <CODE>MediaPlayer</CODE>.  This property specifies
     * a scale factor for the visual components.
     *
     * @return A <CODE>String</CODE> that specifies the zoom factor. Possible values are
     *  <CODE>Scale 1:2</CODE>, <CODE>Scale 1:1</CODE>, <CODE>Scale 2:1</CODE>, 
     *  and <CODE>Scale 4:1</CODE>.
     *
     */
    public String getZoomTo() {
	return this.curZoomLevel;
    }
    
    /**
     *
     * Gets the height of the control <CODE>Component</CODE> for this <CODE>MediaPlayer</CODE>.
     *
     * @return The control <CODE>Component</CODE> height as an integer.
     *
     */
    public int getControlPanelHeight() {
	  
	if (isControlPanelVisible())
	    return controlPanelHeight;
	else
	    return 0;
    }
	
    /**
     *
     * Gets the height of the media-location text field for this <CODE>MediaPlayer</CODE>.
     *
     * @return The height of the media location text field as an integer.
     *
     */
    public int getMediaLocationHeight() {
	  
	if (isMediaLocationVisible())
	    return urlFieldHeight;
	else
	    return 0;
    }
	
    /**
     *
     *  Sets the audio volume property for this <CODE>MediaPlayer</CODE>. 
     *  The volume can range from zero to five, where zero is silence
     *  and five is the maximum volume.
     *
     * @param volumeString A <CODE>String</CODE> that represents the audio volume. 
     * Possible values are "0", "1", "2", "3", "4", or "5".
     *
     */
    public void setVolumeLevel(String volumeString) {
	debug("in setVolumeLevel");
	if (volumeString == null) {
	    return;
	}
	String old = curVolumeLevel;
	int level = Integer.parseInt(volumeString);
	curVolumeLevel = volumeString;
	curVolumeValue = level * 0.2f;

	if (gainControl != null)
	    gainControl.setLevel(curVolumeValue);
		
	changes.firePropertyChange("volumeLevel", old, curVolumeLevel);
	
    }
	
    /**
     *
     * Gets the audio volume property for this <CODE>MediaPlayer</CODE>.
     * The volume can range from zero to five, where zero is silence
     * and five is the maximum volume.
     * @return A <CODE>String</CODE> that represents the audio volume. 
     * Possible values are "0", "1", "2", "3", "4", or "5".
     *
     */
    public String getVolumeLevel() {
	return this.curVolumeLevel;
    }

    /**
     *
     * Gets the MediaLocationVisible property for this <CODE>MediaPlayer</CODE>.
     *
     * @return <CODE>true</CODE> if the media location is visible at runtime, 
     * <CODE>false</CODE> if it is not.
     *
     */
    public boolean isMediaLocationVisible() {
	return displayURL;
    }

    /**
     *
     * Sets the MediaLocationVisible property for this <CODE>MediaPlayer</CODE>.
     *
     * @param val A boolean that indicates whether or not the media location is visible.
     * Set to <CODE>true</CODE> if the media location is visible at runtime,
     * <CODE>false</CODE> if it is not.
     *
     */
    public void setMediaLocationVisible(boolean val) {
	if (displayURL != val) {
	    if (urlName !=null) {
		if (val) { 
		    urlName.setText(urlString);
		    newPanel.add("South",urlName);
		} else {
		    newPanel.remove(urlName);
		}
	    }
	 	  
	    panel.validate();
	    validate();
	    displayURL = val;
	}
    }
    /**
     *
     * Gets the control panel visibility property for this <CODE>MediaPlayer</CODE>.
     * This property indicates whether or not the control panel is visible at runtime.
     *
     * @return <CODE>true</CODE> if the control panel is visible at runtime,
     * <CODE>false</CODE> if it is not.
     *
     */
    public boolean isControlPanelVisible() {
	return panelVisible;
    }

    /**
     *
     * Sets the control panel visibility property for this <CODE>MediaPlayer</CODE>.
     * This property indicates whether or not the control panel is displayed at runtime.
     *
     * @param isVisible A boolean value that indicates whether or not the control panel 
     * should be displayed. Set to <CODE>true</CODE> to display the control panel.
     *
     */
    public void setControlPanelVisible(boolean isVisible) {
	if (panelVisible != isVisible) {
	    boolean old=panelVisible;
	    if (controlComponent != null) {
		if (isVisible) {
		    panel.add("South", controlComponent);
		} else {
		    panel.remove(controlComponent);
		}
	    }
	    invalidate();
	    validate();
	    panelVisible = isVisible;
	    changes.firePropertyChange("controlPanelVisible", new Boolean(old),
				       new Boolean(panelVisible));
	}
	  
    }

    /**
     *
     * Gets the caching control visibility property for this <CODE>MediaPlayer</CODE>.
     * This property indicates whether or not the caching control is displayed at runtime.
     * The caching control displays the download progress for media accessed over the network.
     *
     * @return <CODE>true</CODE> if the control panel is displayed at runtime,
     * <CODE>false</CODE> if it is not.
     *
     */
    public boolean isCachingControlVisible() {
	return cachingVisible;
    }

    /**
     *
     * Sets the caching control visibility property for this <CODE>MediaPlayer</CODE>.
     * This property indicates whether or not the caching control is displayed at runtime.
     * The caching control displays the download progress for media accessed over the network.
     *
     * @param isVisible A boolean value that indicates whether or not the caching control
     *  should be  displayed at
     *   runtime. Set to <CODE>true</CODE> to display the control.
     *
     */
    public void setCachingControlVisible(boolean isVisible) {
	if (cachingVisible != isVisible) {
	    boolean old = cachingVisible;
	    if (cachingComponent != null) {
		if (isVisible) {
		    panel.add("South", cachingComponent);
		} else {
		    panel.remove(cachingComponent);
		}
	    }
	    invalidate();
	    validate();
	    changes.firePropertyChange("cachingControlVisible", new Boolean(old),
				       new Boolean(cachingVisible));  
	    
	}
	cachingVisible = isVisible;
    }

    /**
     *
     * Gets the fixed aspect ratio property for this <CODE>MediaPlayer</CODE>. This 
     * property indicates whether or not the aspect ratio of the video  
     * is maintained when the visual component is resized.
     *
     * @return A boolean value that indicates whether or not the aspect ratio is maintained.
     * Returns <CODE>true</CODE> if it is, <CODE>false</CODE> if it is not.
     *
     */
    public boolean isFixedAspectRatio() {
	return fixedAspectRatio;
    }

    /**
     *
     * Sets the fixed aspect ratio property for this <CODE>MediaPlayer</CODE>. This 
     * property indicates whether or not the aspect ratio of the video  
     * is maintained when the visual component is resized.
     *
     * @param isFixed  A boolean value that indicates whether or not the aspect 
     * ratio should be maintained.
     * Set to <CODE>true</CODE> to maintain the aspect ratio.
     *
     */
    public void setFixedAspectRatio(boolean isFixed) {
	boolean old = fixedAspectRatio;
	fixedAspectRatio = isFixed;
	changes.firePropertyChange("fixedAspectRatio", new Boolean(old),
				   new Boolean(fixedAspectRatio));
    }

    /**
     *
     * Specifies whether or not the Zoom Popup Menu is active.
     * If <CODE>setPopupActive</CODE> is set to false, the popup menu won't be created.
     *
     * @param isActive A boolean value that indicates whether or not the popup menu
     * should be created.
     * Set to <CODE>true</CODE> to activate the popup menu.
     *
     */
    public void setPopupActive(boolean isActive) {
	if (isActive !=isPopupActive) {
	    isPopupActive = isActive; 
	    setPopupActive();
	}
    }
  
    /**
     *
     *   Private class activate popup according to private variable isPopupActive.
     *
     */
    private void setPopupActive() {
	if (isPopupActive ==true)
	    visualComponent.addMouseListener( mouseListen= new visualMouseAdapter());
	else if(mouseListen!=null)
	    visualComponent.removeMouseListener(  mouseListen);
    }
   
    // Player methods

    // method to get the Player's visual component.
 
    public Component getVisualComponent() {
	if (player != null) {
	    return player.getVisualComponent();
	}
	return null;
    }

    // method to get the player's gain control, if there is one.

    public GainControl getGainControl() {
	if (player != null) {
	    return player.getGainControl();
	}
	return null;
    }

    // method to get the player's default control Component.

    public Component getControlPanelComponent() {
	if (player != null) {
	    return player.getControlPanelComponent();
	}
	return null;
    }
  
 
    /**
     *
     * Starts the <CODE>MediaPlayer</CODE> as soon as possible. If the <CODE>MediaPlayer</CODE> does 
     * not already have a <CODE>Player</CODE>, 
     * it creates one and then
     * starts it.
     *
     */
    public void start() {
	if (player != null) {
	    player.start();
	} else {
	    if (mrl ==null) {
		initSetCodeBase();
		mrl=getMediaLocator(urlString);
	    }
	    
	    if (mrl !=null) {
		try {
		    player = javax.media.Manager.createPlayer(mrl);
		    player.addControllerListener(selfListener =
						 new InternalControllerAdapter(this));
		    start();
		    //add original list to new player
		    if (controlListeners.size()>0) {   
			for (int i=0;i<controlListeners.size();i++)
			    player.addControllerListener(
							 (ControllerListener)controlListeners.elementAt(i));
		    }
		} catch (Exception e) {
		    player= null;
		    urlString = " ";
		    Fatal(MediaPlayerResource.getString("UNABLE_CREATE_PLAYER")+e);
		    return;
		}
	    } else 
		Fatal(MediaPlayerResource.getString("COULD_NOT_START_PLAYER"));
	    
	     
	}
    }


    public void addController(Controller n) {
	try {
	    if (player != null) {
		player.addController(n);
	    }
	} catch (IncompatibleTimeBaseException e) {
	    Fatal(MediaPlayerResource.getString("PLAYER_NO_COMPATIBLE_TIME_BASE"));
	    return;
	}
    }
 
    /**
     *
     * Gets the current <CODE>Player</CODE> for this <CODE>MediaPlayer</CODE>. 
     *
     * @return The <CODE>Player</CODE> that this <CODE>MediaPlayer</CODE> is currently using.
     *
     */
    
    public Player getPlayer() {
	return player;
    }
  

    public void removeController(Controller old) {
	if (player != null) {
	    player.removeController(old);
	}
    }

    // MediaHandler method

    // method to set the source used to obtain content.

    public void setSource(DataSource data) {
	try {
	    if (player != null) {
		player.setSource(data);
	    }
	} catch (IOException e) {
	    Fatal(MediaPlayerResource.getString("IO_EXCEPTION")+e);
	    return;
	} catch (IncompatibleSourceException e) {
	    Fatal(MediaPlayerResource.getString("INCOMPATIBLE_SOURCE_EXCEPTION") + e);
	    return;
	}
    }
  
    // Controller methods

    // method to get the current state of the player

    public int getState() {
	if (player != null) {
	    return player.getState();
	} else {
	    return Controller.Unrealized;
	}
    }

    // method to get the target state of the player

    public int getTargetState() {
	if (player != null) {
	    return player.getTargetState();
	} else {
	    return Controller.Unrealized;
	}
    }

    // method to realize the player

    public void realize() {
	if (player != null) {
	    player.realize();
	} else {
	    return;
	}
    }

    //  method to prefetch the player
   
    public void prefetch() {
	if (player != null) {
	    player.prefetch();
	} else {
	    return;
	}
    }

    // method to Abort the current operation and cease any activity
    // that consumes system resources.
  
    public void deallocate() {
	if (player != null) {
	    debug("in deallocate");
	    player.deallocate();
	} else {
	    return;
	   
	}
    }

    // method to release all resources and cease all activity.
 
    public void close() {
	if (player != null) {
	    panel.removeAll();
	    player.close();
	    player = null;
	} else {
	    return;
	}
    }

    // method to get the Controller's start latency in nanoseconds.
  
    public Time getStartLatency() {
	if (player != null) {
	    return player.getStartLatency();
	} else {
	    return LATENCY_UNKNOWN;
	}
    }

    // method to get a list of the Control objects that this Controller supports.
  
    public Control[] getControls() {
	if (player != null) {
	    return player.getControls();
	} else {
	    return new Control[0];
	}
    }

    // method to get the Control that supports the specified class or interface.
 
    public Control getControl(String forName) {
	if (player != null) {
	    return player.getControl(forName);
	} else {
	    return null;
	}
    }

    // method to specify a ControllerListener to which this Controller will send
    // events. A Controller can have multiple ControllerListeners.

    public void addControllerListener(ControllerListener listener) {
	if (player != null) {
	    player.addControllerListener(listener);
	}
	if (!controlListeners.contains(listener))
	    controlListeners.addElement(listener);
    }


    // method to remove the specified listener from this Controller's listener list.
 
    public void removeControllerListener(ControllerListener listener) {
	if (player != null) {
	    player.removeControllerListener(listener);
	}
	if (controlListeners.contains(listener)) {
	    controlListeners.removeElement(listener);
	}
    }

    // Clock methods

    // method to set the TimeBase for this Clock. 
  
    public void setTimeBase(TimeBase master) {
	try {
	    if (player != null) {
		player.setTimeBase(master);
	    }
	} catch (IncompatibleTimeBaseException e) {
	    Fatal(MediaPlayerResource.getString("INCOMPATIBLE_TIME_BASE")+e);
	    return;
	}
    }

    // method to synchronize the current media time to the specified time-base time
    // and start the Clock. 
  
    public void syncStart(Time at) {
	debug("syncStart ");
	if (player != null) {
	    player.syncStart(at);
	}
    }

    // method to stop the Clock. 
  
    public void stop() {
	if (player != null) {
	    player.stop();
	}
    }

    /**
     * Stops the clock and deallocates the system resources used by this
     * player.
     *
     */
    public void stopAndDeallocate() {
	if (player != null) {
	    player.stop();
	}
    }

    // method to set the media time at which you want the Clock to stop.
 
    public void setStopTime(Time stopTime) {
	if (player != null) {
	    player.setStopTime(stopTime);
	}
    }

    // method to get the last value successfully set by setStopTime.
 
    public Time getStopTime() {
	if (player != null) {
	    return player.getStopTime();
	} else {
	    return null;
	}
    }

    // method to set the Clock's media time.

    public void setMediaTime(Time now) {
	if (player != null) {
	    player.setMediaTime(now);
	}
    }

    // method to get this Clock's current media time. 
  
    public Time getMediaTime() {
	if (player != null) {
	    return player.getMediaTime();
	} else {
	    return LATENCY_UNKNOWN;
	}
    }


    /**
     * Gets the current media time in nanoseconds.
     *
     * @return The current media time in nanoseconds.
     *
     */
    public long getMediaNanoseconds() {
	if (player != null) {
	    return player.getMediaNanoseconds();
	} else {
	    return Long.MAX_VALUE;
	}
    }

    // method to get the current media time or the time until this Clock
    // will synchronize to its TimeBase.
  
    public Time getSyncTime() {
	if (player != null) {
	    return player.getSyncTime();
	} else {
	    return LATENCY_UNKNOWN;
	}
    }

    // method to get the TimeBase that this Clock is using.
  
    public TimeBase getTimeBase() {
	if (player != null) {
	    return player.getTimeBase();
	} else {
	    return null;
	}
    }

    // method to get the TimeBase time that corresponds to the specified media time.
 
    public Time mapToTimeBase(Time t) {
	try {
	    if (player != null) {
		return player.mapToTimeBase(t);
	    }
	} catch (ClockStoppedException e) {
	    log(MediaPlayerResource.getString("CALL_A_STOPPED_CLOCK"));
	}

	return LATENCY_UNKNOWN;
    }

    // method to get the current temporal scale factor. 
  
    public float getRate() {
	if (player != null) {
	    return player.getRate();
	} else {
	    return 0.0f;
	}
    }

    // method to set the temporal scale factor. 
  
    public float setRate(float factor) {
	if (player != null) {
	    return player.setRate(factor);
	} else {
	    return 0.0f;
	}
    }

    // Duration method
    // method to get the duration of the media represented by this object.
 
    public Time getDuration() {
	if (player != null) {
	    return player.getDuration();
	} else {
	    return DURATION_UNKNOWN;
	}
    }

  
    /**
     *  Ensures the ability to synchronously
     *  change state. Supports serialization.
     * @param s The state to wait for. The state constants are defined in <CODE>Controller</CODE>.
     * @see Controller#Unrealized
     * @see Controller#Realizing
     * @see Controller#Unrealized
     * @see Controller#Realized
     * @see Controller#Prefetching
     * @see Controller#Prefetched
     * @see Controller#Started
     */
    public synchronized void waitForState(int s) {
	while (state != s) {
	    try {
		wait(1000);
	    } catch (Exception e) {}
	}
    }

    // really simplistic error handling
    Method errMeth = null;

    private void Fatal(String name) {
	// Use reflection to get at the Log.
	try {
	    if (errMeth == null) {
		Class cls = Class.forName("com.sun.media.Log");  
		Class params[] = new Class [] 
		{ Class.forName("java.lang.Object") };
		errMeth = cls.getMethod("error", params);
	    }
	    Object args[] = new Object [] { name };
	    errMeth.invoke(null, args);
	} catch (Throwable t) {
	    System.err.println(name);
	}
    }

    private void log(String name) {
	// Use reflection to get at the Log.
	try {
	    if (errMeth == null) {
		Class cls = Class.forName("com.sun.media.Log");  
		Class params[] = new Class [] 
		{ Class.forName("java.lang.Object") };
		errMeth = cls.getMethod("comment", params);
	    }
	    Object args[] = new Object [] { name };
	    errMeth.invoke(null, args);
	} catch (Throwable t) {
	    System.err.println(name);
	}
    }


    // Externalizable methods
    /**
     * Restores the contents of this object
     *
     * @param in The stream that can read primitive and object
     *   data types.
     *
     * @exception IOException If an IO error occurs while try to read the object.
     * @exception java.lang.ClassNotFoundException If no definition for the class can be found.
     */
    public void readExternal(ObjectInput in) throws IOException, 
	ClassNotFoundException {
	//save the component state
	setBounds((Rectangle)in.readObject());
	setBackground((Color) in.readObject());
	setForeground((Color) in.readObject());
	setFont((Font) in.readObject());
	setVisible(in.readBoolean());
	setEnabled(in.readBoolean());
	//restore MediaPlayer state
	String n = (String)(in.readObject());
	if (n != null) {
	    mrl = new MediaLocator(n);
	}
	if (mrl !=null) {
	    try {
		setMediaLocator(mrl);
	    } catch (Exception e) { }
	}
	setMediaLocationVisible(in.readBoolean());
	panelVisible = in.readBoolean();
	cachingVisible = in.readBoolean();
	fixedAspectRatio = in.readBoolean();
	preferredHeight = in.readInt();
	preferredWidth = in.readInt();
	  
	//restore Player state if appropriate
	if (in.readBoolean()) {
	    int s = in.readInt();
	    int ts = in.readInt();
	    state = Unrealized;
	    if (s >= Realized) {
		long mt = in.readLong();
		long st = in.readLong();
		float r = in.readFloat();
		if (ts >= Prefetched) {
		    player.prefetch();
		    waitForState(Prefetched);
		} else if (ts >= Realized) {
		    player.realize();
		    waitForState(Realized);
		}
		player.setMediaTime(new Time(mt));
		player.setStopTime(new Time(st));
		player.setRate(r);
		float l = in.readFloat();
		if (l != -1.0F) {
		    GainControl g = player.getGainControl();
		    if (g != null) {
			boolean mute = in.readBoolean();
			g.setLevel(l);
			g.setMute(mute);
		    } else {
			in.readBoolean();
		    }
		}
		    
		if (ts >= Started) {
		    player.start();
		}
	    }
	}
	invalidate();
	validate();
	  	  
    }

    /**
     *
     * Saves the contents of this object by calling the <CODE>ObjectOutput</CODE>
     * <CODE>writeObject</CODE> method.
     *
     * @param out The stream that can read primitive and object
     *   data types.
     * @exception IOException If an IO error occurs while attempting to save the object.
     */
    public void writeExternal(ObjectOutput out) throws IOException {
	//save the component state
	out.writeObject(getBounds());
	out.writeObject(getBackground());
	out.writeObject(getForeground());
	out.writeObject(getFont());
	out.writeBoolean(isVisible());
	out.writeBoolean(isEnabled());
	//save the MediaPlayer state
	
	if (mrl !=null) {
	    out.writeObject(mrl.toExternalForm());
	} else {
	    out.writeObject(null);
	}
	out.writeBoolean(displayURL);
	out.writeBoolean(panelVisible);  
	out.writeBoolean(cachingVisible); 
	out.writeBoolean(fixedAspectRatio);
	out.writeInt(preferredHeight);
	out.writeInt(preferredWidth);
	//save the Player state if appropriate
	if (player == null) {
	    out.writeBoolean(false);
	} else {
	    out.writeBoolean(true);
	    out.writeInt(player.getState());
	    out.writeInt(player.getTargetState());
	    if (player.getState() >= Realized) {
		out.writeLong(player.getMediaNanoseconds());
		out.writeLong(player.getStopTime().getNanoseconds());
		out.writeFloat(player.getRate());
		//we need to save Gain information:
		GainControl g;
		if ((g = player.getGainControl()) != null) {
		    out.writeFloat(g.getLevel());
		    out.writeBoolean(g.getMute());
		} else {
		    out.writeFloat(-1.0F);
		}
	    }
	}
    }
  
    /**
     *
     * zoomTo():
     *   method to handle the resize of the visual component of a player
     *   according to its input scale.
     *
     * @param float z: the scale for resizing.
     *
     */
    private void zoomTo(float z) {
	debug("zoomTo "+z);
	int ddwidth=0;
	int ddheight=0;
	if ((visualComponent != null) && fitVideo) {
	    try {
		Dimension d=visualComponent.getPreferredSize();
		ddwidth = (int)(d.width * z);
		ddheight = (int)(d.height * z);
      
		int dheight=0;
		if ((controlComponent != null) && isControlPanelVisible())
		    dheight = controlComponent.getPreferredSize().height;
		if (displayURL ==true)
		    dheight += urlName.getPreferredSize().height;
		    
		if (newPanel!=null)
		    newPanel.setSize(ddwidth,dheight);
		panel.setSize(ddwidth , ddheight+dheight );	
      			    		      
		if ((fixedAspectRatio ==true) || 
		    ((fixedAspectRatio==false)&&(fixtedFirstTime==false)))  
		    center(this,panel,true,dheight);
		else
		    center(this,panel,false,dheight);
		  
		panel.validate();
  	
	    } catch (Exception e) {
		log(MediaPlayerResource.getString("UNABLE_TO_ZOOM")+e);
	    }
		   
	}
    }
  
    /**
     *
     * aspectRatio():
     *   calculate the aspect Ratio according to the width, height and control 
     *   component, MediaLocation text height if they exist
     *
     * @param float width:  width of panel.
     *        float height:  height of panel.
     *        int   controllerHeight:  including height of control component
     *                                 and mediaLocation height
     *
     */
    private float aspectRatio(float width, float height,
			      int controllerHeight) {
	return (width/((float) (height-controllerHeight)));
    }

    /**
     *
     * center():
     *   This method moves the component to the center of parent 
     *   container. If fixAspectRatio is specified, then the width
     *   and height will be adjusted accordingly.
     *
     * @param parent  parent container
     *        comp    component to be added to parent
     *        fit     if maintain Aspect Ratio
     *        dheight height of controlComponent 
     */
    
    private void center(Container parent, Panel comp, boolean fit, int dheight) {
	int pwidth = parent.getSize().width;
	int pheight = parent.getSize().height;
  
	int width = comp.getSize().width;
	int height = comp.getSize().height;
	if (fit) {
	    float aspect = aspectRatio((float) width,(float) height, dheight);
	    if (width > pwidth) {
		height = (int) ((float)pwidth/aspect) + dheight;
		if (height > pheight) {
		    width = (int) (aspect*(pheight-dheight));
		    height = pheight;
		} else
		    width = pwidth;
		comp.setBounds(parent.getBounds().x,parent.getBounds().y,width,height);
	    } else if (height > pheight) {
		width = (int) (aspect*(pheight-dheight));
		height = pheight;
           	comp.setBounds(parent.getBounds().x,parent.getBounds().y,width,height);
	    }
	}
	comp.setLocation((pwidth/2)-(width/2), (pheight/2)-(height/2));
	comp.setSize( width,height);
    }

  
    /**
     *
     * addPopupMenu():
     *   popup menu for some preset scales of the visualComponent
     *
     * @param Component visual: the visual component to add the popup menu
     *   to.
     *
     */
    
    private void addPopupMenu(Component visual) {
	MenuItem mi;
	ActionListener zoomSelect;

	zoomMenu = new PopupMenu("Zoom");
	zoomSelect = new popupActionListener();
	visual.add(zoomMenu);
	mi = new MenuItem("Scale 1:2");
	zoomMenu.add(mi);
	mi.addActionListener(zoomSelect);
	mi = new MenuItem("Scale 1:1");
	zoomMenu.add(mi);
	mi.addActionListener(zoomSelect);
	mi = new MenuItem("Scale 2:1");
	zoomMenu.add(mi);
	mi.addActionListener(zoomSelect);
	mi = new MenuItem("Scale 4:1");
	zoomMenu.add(mi);
	mi.addActionListener(zoomSelect);
	   
    }
  
   
    /**
     * Resizes the visual <CODE>Component</CODE>, control <CODE>Component</CODE>,
     * and <CODE>urlName</CODE> according to  the specified dimensions.
     *   
     *
     * @param x The x coordinate of the rectangle.
     * @param y The y coordinate of the rectangle.
     * @param w The width of the rectangle.
     * @param h The height of the rectangle.
     *
     */
    public void setBounds(int x, int y, int w, int h) {
	debug("setBounds "+x +" "+y+" "+w+" "+h+" ");
	super.setBounds(x,y,w,h);
	Dimension d = getSize();
	int pheight = d.height;
	int pwidth = d.width;
	int p = 0;
	int totalHeight = 0;
	  
	if ((urlName!=null) &&(isMediaLocationVisible())) {
	    totalHeight = urlFieldHeight=urlName.getPreferredSize().height;
	    if ((pheight < 5) && (displayURL==true)) {
		pheight = 5;
	    }
	}    
	if ((controlComponent != null) && isControlPanelVisible()) {
	    controlPanelHeight=controlComponent.getPreferredSize().height;
	    totalHeight +=controlPanelHeight;
	   
	    if (d.width < 160)
		d.width = 160;
	    if ((pheight < 2) && (visualComponent != null))
		{
		    pheight += 2;
		}
	  
	}
	 
	if (visualComponent !=null) {
	    Dimension vSize = visualComponent.getPreferredSize();
	    if (fixedAspectRatio == true) {
		//compare the ratio of parent and panel, and get the smaller number
		if (((float)pwidth/(float)vSize.width) >=
		    ((float)(pheight-totalHeight)/
		     (float)(vSize.height-totalHeight)))
		    curZoomValue =  
			((float)(pheight-totalHeight)/
			 (float)(vSize.height-totalHeight));
		else    
		    curZoomValue = (float)pwidth/(float)vSize.width;
		
		if (curZoomValue <1.0)
		    curZoomValue=1;
		zoomTo(curZoomValue);
	    } else
		panel.setBounds(getBounds());
	   
	} else {
	    panel.setSize(getSize());
	    validate();
	}
	
    }

    /**
     *
     * Gets the dimensions of the preferred size.
     *
     * @return A <CODE>Dimension</CODE> that represents the preferred size.
     *
     */
    public Dimension getPreferredSize() {
	return new Dimension(preferredWidth, preferredHeight);
    }

    /**
     *
     * Adds a <CODE>PropertyChangeListener</CODE> to the listener list.
     *
     * @param c The <CODE>PropertyChangeListener</CODE> to be
     * added.
     *
     */
    public void addPropertyChangeListener(PropertyChangeListener c) {
	changes.addPropertyChangeListener(c);
    }

    /**
     *
     * Removes a <CODE>PropertyChangeListener</CODE> from the listener list.
     *
     * @param c The <CODE>PropertyChangeListener</CODE> to be
     *   removed.
     *
     */
    public void removePropertyChangeListener(PropertyChangeListener c) {
	changes.removePropertyChangeListener(c);
    }

    /**
     * Sets the codebase of the running applet.
     *
     * @param cb The <CODE>URL</CODE> for the codebase of the currently running applet.
     *
     */
    public void setCodeBase(URL cb) {
	mpCodeBase = cb;
    }

    /**
     * An inner class that's a  component listener for receiving component events.
     */
    
    private class visualComponentAdapter extends ComponentAdapter {
	private MediaPlayer thisBean;
    
	/**
	 * Constructor.
	 *
	 * @param b The instance of a <CODE>MediaPlayer</CODE> bean.
	 *
	 */
	public visualComponentAdapter(MediaPlayer b) {
	    super();
	    thisBean = b;
	}
    
	/**
	 *  Gets the notification that the bean has been resized and centers the panel inside the bean.
	 *
	 * @param ce The <CODE>Component</CODE> event.
	 *
	 */
	public void componentResized(ComponentEvent ce) {
     
	    if (ce.getSource() == thisBean) {
		debug("componentResized");
		int dheight=0;
		if ((controlComponent != null) && isControlPanelVisible())
		    dheight =controlComponent.getPreferredSize().height;
		center(thisBean,panel,isFixedAspectRatio(),dheight);
	    }
      
	}
   
    }
   
    /**
     *   An internal class for receiving mouse events.  Only need to implement
     *   the  methods we are interested in.
     *
     */
    private class visualMouseAdapter extends MouseAdapter {
 
	/**
	 *
	 *   Displays the zoomMenu according to the x, y values from the input <CODE>MouseEvent</CODE>.
	 *
	 * @param me: The <CODE>MouseEvent</CODE> that contains the mouse action information.
	 *
	 */
	public void mousePressed(MouseEvent me) {
	    if (me.isPopupTrigger()) {
		zoomMenu.show(visualComponent, me.getX(), me.getY());
	    }
	}

	/**
	 *
	 *   Displays the zoomMenu according the to x, y values from the input <CODE>MouseEvent</CODE>.
	 *
	 * @param me The <CODE>MouseEvent</CODE> that contains the mouse action information.
	 *
	 */
	public void mouseReleased(MouseEvent me) {
	    if (me.isPopupTrigger()) {
		zoomMenu.show(visualComponent, me.getX(), me.getY());
	    }
	}

	/**
	 *
	 *   Displays the zoomMenu according to the  x, y values from the input <CODE>MouseEvent</CODE>.
	 *
	 * @param me The <CODE>MouseEvent</CODE>  that contains the mouse action information.
	 *
	 */
	public void mouseClicked(MouseEvent me) {
	    if (me.isPopupTrigger()) {
		zoomMenu.show(visualComponent, me.getX(), me.getY());
	    }
	}
	  
    }

    /**
     *
     * An <CODE>ActionListener</CODE> for zoom panel selection.
     *
     */
    
    private class popupActionListener implements ActionListener {
	/**
	 *   Calls setZoomTo to resize the visual
	 *   component according to the option the user was chosen.
	 *
	 * @param ae  Contains the information about
	 *   the selection of the zoomMenu.
	 *
	 */
	public void actionPerformed(ActionEvent ae) {
	    String action = ae.getActionCommand();
	    setZoomTo(action);
	}
    }

 
    /**
     *   Saves the media stop time to a variable.
     *
     */
    public void saveMediaTime() {
	mediaTime = getMediaTime();
    }

    /**
     *   Restores the media time saved by <CODE>saveMediaTime</CODE>
     *   so that the video resumes from the time it was stoped.
     *
     */
    public void restoreMediaTime() {
	setMediaTime(mediaTime);
    }

  
    //{{DECLARE_CONTROLS
    //}}
    private void debug(String s) {
	//	  System.out.println(s);
    }
}

