/* * @(#)BasicPlayer.java 1.81 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package com.sun.media; import java.security.*; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.io.*; import java.net.URL; import java.awt.*; import java.awt.image.*; import java.util.*; import javax.media.*; import javax.media.format.*; import javax.media.protocol.*; import javax.media.control.BufferControl; import com.sun.media.util.*; import com.sun.media.controls.*; import com.sun.media.ui.*; import com.ms.security.PermissionID; import com.ms.security.PolicyEngine; /** * BasicPlayer implements the bases of a javax.media.Player. It handles * all the Player state transitions, event handling and management of * any Controller under its control. */ public abstract class BasicPlayer extends BasicController implements Player, ControllerListener, DownloadProgressListener { static public String VERSION = JMFI18N.getResource("mediaplayer.version"); // private MediaSource source = null; protected javax.media.protocol.DataSource source = null; protected Vector controllerList = new Vector(); private Vector optionalControllerList = new Vector(); private Vector removedControllerList = new Vector(); private Vector currentControllerList = new Vector(); private Vector potentialEventsList = null; private Vector receivedEventList = new Vector(); private boolean receivedAllEvents = false; private Vector configureEventList = new Vector(); private Vector realizeEventList = new Vector(); private Vector prefetchEventList = new Vector(); private Vector stopEventList = new Vector(); private ControllerEvent CachingControlEvent = null; private Controller restartFrom = null; private Vector eomEventsReceivedFrom = new Vector(); private Vector stopAtTimeReceivedFrom = new Vector(); private PlayThread playThread = null; private StatsThread statsThread = null; private Time duration = DURATION_UNKNOWN; private Time startTime, mediaTimeAtStart; private boolean aboutToRestart = false; private boolean closing = false; private boolean prefetchFailed = false; protected boolean framePositioning = true; protected Control [] controls = null; protected Component controlComp = null; // Information controls public SliderRegionControl regionControl = null; protected CachingControl cachingControl = null; protected ExtendedCachingControl extendedCachingControl = null; protected BufferControl bufferControl = null; private Object startSync = new Object(); private Object mediaTimeSync = new Object(); private static JMFSecurity jmfSecurity = null; private static boolean securityPrivelege=false; private Method m[] = new Method[1]; private Class cl[] = new Class[1]; private Object args[][] = new Object[1][0]; static { try { jmfSecurity = JMFSecurityManager.getJMFSecurity(); securityPrivelege = true; } catch (SecurityException e) { } } public BasicPlayer() { configureEventList.addElement("javax.media.ConfigureCompleteEvent"); configureEventList.addElement("javax.media.ResourceUnavailableEvent"); realizeEventList.addElement("javax.media.RealizeCompleteEvent"); realizeEventList.addElement("javax.media.ResourceUnavailableEvent"); prefetchEventList.addElement("javax.media.PrefetchCompleteEvent"); prefetchEventList.addElement("javax.media.ResourceUnavailableEvent"); stopEventList.addElement("javax.media.StopEvent"); stopEventList.addElement("javax.media.StopByRequestEvent"); stopEventList.addElement("javax.media.StopAtTimeEvent"); stopThreadEnabled = false; } /** * Will return true if the player can do frame * positioning. Hack for now, should be removed when players * actually implement the framePositioning control. */ public boolean isFramePositionable(){ return framePositioning; } /** * A player is not configurable. */ protected boolean isConfigurable() { return false; } /** * Set the DataSource that provides the media for this player. * BasicPlayer only supports PullDataSource by default. Subclasses * can override this method to support other DataSources. * * @param source of media for this player. * @exception IOException thrown when an i/o error occurs * in reading information from the data source. * @exception IncompatibleSourceException thrown if the Player * can't use this source. */ public void setSource(javax.media.protocol.DataSource source) throws IOException, IncompatibleSourceException { this.source = source; try { cachingControl = (CachingControl) source.getControl("javax.media.CachingControl"); if ( (cachingControl != null) && (cachingControl instanceof ExtendedCachingControl) ) { extendedCachingControl = (ExtendedCachingControl) cachingControl; if (extendedCachingControl != null) { // update progress every 100 kilobytes regionControl = new SliderRegionControlAdapter(); extendedCachingControl.addDownloadProgressListener(this, 100); } } } catch (ClassCastException e) { } } public void downloadUpdate() { if (extendedCachingControl == null) return; // It will be nice if we can avoid the cast to BasicController sendEvent(new CachingControlEvent(this, cachingControl, cachingControl.getContentProgress())); if ( regionControl == null ) return; long contentLength = cachingControl.getContentLength(); int maxValuePercent; if ( (contentLength == javax.media.protocol.SourceStream.LENGTH_UNKNOWN) || (contentLength <= 0) ) { maxValuePercent = 0; } else { long endOffset = extendedCachingControl.getEndOffset(); maxValuePercent = (int) ((100.0 * endOffset) / contentLength); if (maxValuePercent < 0) maxValuePercent = 0; else if (maxValuePercent > 100) maxValuePercent = 100; } regionControl.setMinValue(0); regionControl.setMaxValue(maxValuePercent); } public MediaLocator getMediaLocator() { if (source != null) return source.getLocator(); return null; } public String getContentType() { if (source != null) return source.getContentType(); return null; } /** * Get the DataSource used by this player. * @return the DataSource used by this player. */ protected javax.media.protocol.DataSource getSource() { return source; } /** * This is called when close() is invoked on the Player. close() takes * care of the general behavior before invoking doClose(). Subclasses * should implement this only if it needs to do something specific to * close the player. */ protected void doClose() { synchronized (this) { closing = true; notifyAll(); } if (getState() == Controller.Started) { // Stop everything first. stop(LOCAL_STOP); } // Ask all its controllers to close themselves. if (controllerList != null) { Controller c; while (!controllerList.isEmpty()) { c = (Controller)controllerList.firstElement(); c.close(); controllerList.removeElement(c); } } // Close the ui components. if (controlComp != null) ((DefaultControlPanel)controlComp).dispose(); controlComp = null; if (statsThread != null) statsThread.kill(); sendEvent(new ControllerClosedEvent(this)); } /** * Assigns a timebase for the player. * If the BasicPlayer plays back audio, the timebase can be none * other than the master timebase as returned by getMasterTimeBase(). * This is to ensure that we won't set to a timebase the audio * cannot handle at this point. * If the playback is video only, the timebase can be set to any * timebase desired. * @param tb time base to be used by the Player. * @exception IncompatibleTimeBaseException thrown when a time base other * than the master time base is set when audio is enabled. */ public void setTimeBase(TimeBase tb) throws IncompatibleTimeBaseException{ TimeBase oldTimeBase = getMasterTimeBase(); if (tb == null) tb = oldTimeBase; Controller c = null; int i; if (controllerList != null) { try { i = controllerList.size(); while (--i >= 0) { c = (Controller)controllerList.elementAt(i); c.setTimeBase(tb); } } catch (IncompatibleTimeBaseException e) { // SetTimeBase had failed on one Controller. Some // controllers may have already assigned the new timeBase // We'll need to reverse that now. Controller cx; i = controllerList.size(); while (--i >= 0) { cx = (Controller)controllerList.elementAt(i); if (cx == c) break; cx.setTimeBase(oldTimeBase); } Log.dumpStack(e); throw e; } } super.setTimeBase(tb); } /** * Set the upper bound of the media time. * @param duration the duration in nanoseconds. */ protected void setMediaLength(long t) { duration = new Time(t); super.setMediaLength(t); } long lastTime = 0; /** * Get the duration of the movie. * @return the duration of the movie. */ public Time getDuration() { long t; if ((t = getMediaNanoseconds()) > lastTime) { lastTime = t; updateDuration(); } return duration; } protected synchronized void updateDuration() { Time oldDuration = duration; duration = DURATION_UNKNOWN; for (int i = 0; i < controllerList.size(); i++) { Controller c = (Controller) controllerList.elementAt(i); Time dur = c.getDuration(); if ( dur.equals(DURATION_UNKNOWN) ) { if (! (c instanceof BasicController)) { duration = DURATION_UNKNOWN; break; } } else if (dur.equals(DURATION_UNBOUNDED)) { duration = DURATION_UNBOUNDED; break; } else { if (duration.equals(DURATION_UNKNOWN)) duration = dur; else if (duration.getNanoseconds() < dur.getNanoseconds()) duration = dur; } } if (duration.getNanoseconds() != oldDuration.getNanoseconds()) { setMediaLength(duration.getNanoseconds()); sendEvent( new DurationUpdateEvent(this, duration) ); } } public Time getStartLatency() { super.getStartLatency(); Time latency; long t = 0; // Find the longest start latency from all the slave Controllers. for (int i = 0; i < controllerList.size(); i++) { Controller c = (Controller) controllerList.elementAt(i); latency = c.getStartLatency(); if (latency == LATENCY_UNKNOWN) continue; if (latency.getNanoseconds() > t) t = latency.getNanoseconds(); } if (t == 0) return LATENCY_UNKNOWN; return new Time(t); } /** * Stop because stop time has been reached. * Subclasses should override this method. */ protected void stopAtTime() { // We'll overwrite the parent method which stops the controller. // For the player, we don't have to do anything in particular // since the controllers are supposed to stop themselves. } /** * This is for subclass to access Controller's implementation of stopAtTime. */ protected void controllerStopAtTime() { super.stopAtTime(); } public void setStopTime(Time t) { if (state < Realized) { throwError(new NotRealizedError("Cannot set stop time on an unrealized controller.")); } if (getClock().getStopTime() == null || getClock().getStopTime().getNanoseconds() != t.getNanoseconds()) { sendEvent(new StopTimeChangeEvent(this, t)); } doSetStopTime(t); } private void doSetStopTime(Time t) { getClock().setStopTime(t); Vector list = controllerList; int i = list.size(); while (--i >= 0) { Controller c = (Controller)controllerList.elementAt(i); c.setStopTime(t); } } /** * This is for subclass to access Controller's implementation of setStopTime. */ protected void controllerSetStopTime(Time t) { super.setStopTime(t); } /** * Loops through the list of controllers maintained by this * player and invoke setMediaTime on each of them. * This is a "final" method and cannot be overridden by subclasses. * @param now the target media time. **/ public final void setMediaTime(Time now) { if (state < Realized) throwError(new NotRealizedError(MediaTimeError)); // Set Media time from EOM and user click on the slider is // trampling on one another. Causing the player to hang // this mediaTimeSync will guard against that. synchronized (mediaTimeSync) { if (syncStartInProgress()) return; if (getState() == Controller.Started) { aboutToRestart = true; stop(RESTARTING); } // If source is Positionable, we'll take care of this // at the top level. if (source instanceof Positionable) now = ((Positionable)source).setPosition(now, Positionable.RoundDown); super.setMediaTime(now); int i = controllerList.size(); while (--i >= 0) { ((Controller)controllerList.elementAt(i)).setMediaTime(now); } // For subclasses to add in their own behavior. doSetMediaTime(now); if (aboutToRestart) { syncStart(getTimeBase().getTime()); aboutToRestart = false; } } } /** * Return true if the player is about to restart again. */ public boolean isAboutToRestart() { return aboutToRestart; } /** * Called from setMediaTime. * This is used for subclasses to add in their own behavior. * @param now the target media time. */ protected void doSetMediaTime(Time now) { } /** * Get the Component this player will output its visual media to. If * this player has no visual component (e.g. audio only) * getVisualComponent() will return null. * Subclasses should override this method and return the visual * component but call this method first to ensure that the restrictions * on player methods are enforced. * @return the media display component. */ public Component getVisualComponent() { int state = getState(); if (state < Realized) { throwError(new NotRealizedError("Cannot get visual component on an unrealized player")); } return null; } /** * Get the Component with the default user interface for controlling * this player. * If this player has no default control panel null is * returned. * Subclasses should override this method and return the control panel * component but call this method first to ensure that the restrictions * on player methods are enforced. * @return the default control panel GUI. */ public Component getControlPanelComponent() { int state = getState(); if (state < Realized) { throwError(new NotRealizedError("Cannot get control panel component on an unrealized player")); } if (controlComp == null) { controlComp = new DefaultControlPanel( this ); } return controlComp; } /** * Get the object for controlling audio gain. Return null * if this player does not have a GainControl (e.g. no audio). * * @return the GainControl object for this player. */ public GainControl getGainControl() { int state = getState(); if (state < Realized) { throwError(new NotRealizedError("Cannot get gain control on an unrealized player")); } else { return (GainControl)getControl("javax.media.GainControl"); } return null; } /** * Return the list of controls from its slave controllers plus the * ones that this player supports. * @return the list of controls supported by this player. */ public Control [] getControls() { if (controls != null) return controls; // build the list of controls. It is the total of all the // controls from each controllers plus the ones that are maintained // by the player itself (e.g. playbackControl). Vector cv = new Vector(); if (cachingControl != null) cv.addElement(cachingControl); if (bufferControl != null) cv.addElement(bufferControl); Control c; Object cs[]; Controller ctrller; int i, size = controllerList.size(); for (i = 0; i < size; i++) { ctrller = (Controller)controllerList.elementAt(i); cs = ctrller.getControls(); if (cs == null) continue; for (int j = 0; j < cs.length; j++) { cv.addElement(cs[j]); } } Control ctrls[]; size = cv.size(); ctrls = new Control[size]; for (i = 0; i < size; i++) { ctrls[i] = (Control)cv.elementAt(i); } // If the player has already been realized, we'll save what // we've collected this time. Then next time, we won't need // to go through this expensive search again. if (getState() >= Realized) controls = ctrls; return ctrls; } /** * This get called when some Controller notifies this player of * any event. */ final public void controllerUpdate(ControllerEvent evt) { processEvent(evt); } /** * Return the list of BasicControllers supported by this Player. * @return a vector of the BasicControllers supported by this Player. */ public final Vector getControllerList() { return controllerList; } private Vector getPotentialEventsList() { return potentialEventsList; } /** * Resets the list of received events */ private void resetReceivedEventList() { if (receivedEventList != null) receivedEventList.removeAllElements(); } /** * Return the list of received events */ private Vector getReceivedEventsList() { return receivedEventList; } /** * Updates the list of received events. Sources are stored. */ private void updateReceivedEventsList(ControllerEvent event) { if (receivedEventList != null) { Controller source = event.getSourceController(); if (receivedEventList.contains(source)) { // System.out.println("DUPLICATE " + event + // " received from: " + source); return; } receivedEventList.addElement(source); } } /** * Start the Player as soon as possible. * Start attempts to transition the player into the * started state. * If the player has not been realized, or prefetched, * then the equivalent of those actions will occur, * and the appropriate events will be generated. * If the implied realize or prefetch fail, a failure * event will be generated and the Player will remain in * one of the non-started states.<p> * This is a "final" method. Subclasses should override doStart() to * implement its own specific behavior. */ public final void start() { synchronized (startSync) { if (restartFrom != null) { return; } if (getState() == Started) { sendEvent(new StartEvent(this, Started, Started, Started, mediaTimeAtStart, startTime)); return; // ignored according to jmf spec. } if ( (playThread == null) || (! playThread.isAlive()) ) { setTargetState(Started); if ( /*securityPrivelege && */ (jmfSecurity != null) ) { String permission = null; try { if (jmfSecurity.getName().startsWith("jmf-security")) { permission = "thread"; jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD); m[0].invoke(cl[0], args[0]); permission = "thread group"; jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP); m[0].invoke(cl[0], args[0]); } else if (jmfSecurity.getName().startsWith("internet")) { PolicyEngine.checkPermission(PermissionID.THREAD); PolicyEngine.assertPermission(PermissionID.THREAD); } } catch (Throwable e) { if (JMFSecurityManager.DEBUG) { System.err.println("Unable to get " + permission + " privilege " + e); } securityPrivelege = false; // TODO: Do the right thing if permissions cannot be obtained. // User should be notified via an event } } if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) { try { Constructor cons = CreateWorkThreadAction.cons; playThread = (PlayThread) jdk12.doPrivM.invoke( jdk12.ac, new Object[] { cons.newInstance( new Object[] { PlayThread.class, BasicPlayer.class, this })}); playThread.start(); } catch (Exception e) { } } else { playThread = new PlayThread(this); playThread.start(); } } else { // $$$$ // System.out.print("WARNING: playThread is alive. start ignored"); // $$$ // System.out.println(": MP State: " + getState()); } } // startSync } /** * Start at the given time base time. * This overrides Clock.syncStart() and obeys all its semantics.<p> * This is a "final" method. Subclasses should override doStart() to * implement its own specific behavior. * @param tbt the time base time to start the player. */ public final void syncStart(Time tbt) { /** * To guard against conflict with setMediaTime. */ synchronized (mediaTimeSync) { if (syncStartInProgress()) return; int state = getState(); if (state == Started) { throwError(new ClockStartedError("syncStart() cannot be used on an already started player")); } if (state != Prefetched) { throwError(new NotPrefetchedError("Cannot start player before it has been prefetched")); } // Clear the EOM and StopAtTime lists. eomEventsReceivedFrom.removeAllElements(); stopAtTimeReceivedFrom.removeAllElements(); setTargetState(Started); int i = controllerList.size(); // The start(tbt) will throw a NotPrefetchedError if // a controller is not in Prefetched state while (--i >= 0) { if (getTargetState() == Started) { // ADDED ((Controller)controllerList.elementAt(i)).syncStart(tbt); } } if (getTargetState() == Started) { // ADDED // If control comes here, the controllers // are in Started state. startTime = tbt; mediaTimeAtStart = getMediaTime(); super.syncStart(tbt); // To start the clock and set the state to Started } } } /** * Invoked by start() or syncstart(). * Called from a separate thread called TimedStart thread. * subclasses can override this method to implement its specific behavior. */ protected void doStart() { } /** * This method gets run in a separate thread called PlayThread */ final synchronized void play() { boolean status; // If a deallocate() happens before this thread gets to run. // or if a stop() happens before this thread gets to run. if (getTargetState() != Started) { return; } prefetchFailed = false; // The following completed checks should be looked at seriously. // It should be something like (state < Prefetched) etc. // It's too late for 2.1.1 release. We'll leave it this way. --ivg int state = getState(); if ( (state == Unrealized) || (state == Configured) || (state == Realized) ) { prefetch(); } while (!closing && !prefetchFailed && (getState() == Configuring || getState() == Realizing || getState() == Realized || getState() == Prefetching)) { try { wait(); } catch (InterruptedException e) { } } if ( getState() != Started && getTargetState() == Started && getState() == Prefetched ) { syncStart(getTimeBase().getTime()); } } /** * The stop() method calls doStop() so that subclasses can * add additional behavior. */ protected void doStop() { } /** * Stop the player. * If current state is Started, sends stop() to all the * managed controllers, and waits for a StopEvent from * all of them. It then sends a StopEvent to any listener(s). */ public final /*synchronized*/ void stop() { stop(STOP_BY_REQUEST); } static final int LOCAL_STOP = 0; static final int STOP_BY_REQUEST = 1; static final int RESTARTING = 2; private /*synchronized*/ void stop(int stopType) { int state; switch (state = getState()) { case Unrealized: case Realized: case Prefetched: setTargetState(state); break; case Realizing: setTargetState(Realized); break; case Prefetching: case Started: setTargetState(Prefetched); break; } if (getState() != Started) { switch (stopType) { case STOP_BY_REQUEST: sendEvent( new StopByRequestEvent(this, getState(), getState(), getTargetState(), getMediaTime())); break; case RESTARTING: sendEvent( new RestartingEvent(this, getState(), getState(), Started, getMediaTime())); break; default: sendEvent( new StopEvent(this, getState(), getState(), getTargetState(), getMediaTime())); break; } } else if (getState() == Started) { synchronized(this) { // List of potential events for stop() potentialEventsList = stopEventList; // Reset list of received events resetReceivedEventList(); receivedAllEvents = false; currentControllerList.removeAllElements(); int i = controllerList.size(); while (--i >= 0) { Controller c = (Controller) controllerList.elementAt(i); currentControllerList.addElement(c); c.stop(); } if (currentControllerList == null) return; if (!currentControllerList.isEmpty()) { try { while (!closing && !receivedAllEvents) wait(); } catch (InterruptedException e) { } currentControllerList.removeAllElements(); } super.stop(); //doStop(); // Allow subclasses to extend the behavior switch (stopType) { case STOP_BY_REQUEST: sendEvent( new StopByRequestEvent(this, Started, getState(), getTargetState(), getMediaTime())); break; case RESTARTING: sendEvent( new RestartingEvent(this, Started, getState(), Started, getMediaTime())); break; default: sendEvent( new StopEvent(this, Started, getState(), getTargetState(), getMediaTime())); break; } } } } protected final void processEndOfMedia() { super.stop(); sendEvent(new EndOfMediaEvent(this, Started, Prefetched, getTargetState(), getMediaTime())); } /** * Add a Controller to the list of Controllers under this Player's * management. This is a protected method use only by subclasses. * Use addController() for public access. */ protected final void manageController(Controller controller) { manageController(controller, false); } /** * Add a Controller to the list of Controllers under this Player's * management. */ protected final void manageController(Controller controller, boolean optional) { if (controller != null) { if (!controllerList.contains(controller)) { controllerList.addElement(controller); if (optional) optionalControllerList.addElement(controller); controller.addControllerListener(this); } } updateDuration(); } /** * Remove a Controller from the list of Controllers under this Player's * management. This is a protected method use only by subclasses. * Use removeController() for public access. */ public final void unmanageController(Controller controller) { if (controller != null) if (controllerList.contains(controller)) { controllerList.removeElement(controller); controller.removeControllerListener(this); } } /** * Assume control of another Controller. * A Player can accept responsibility for controlling * another Controller. * Once a Controller has been added * this Player will: * <ul> * <li> Slave the Controller to the Player's time-base. * <li> Use the Controller in the Player's computation * of start latency. * The value the Player returns in its <b>getStartLatency</b> * method is the larger * of: <b>getStartLatency</b> before the Controller was added, or * <b>getStartLatency</b> of the Controller. * <li> Pass along, as is appropriate, events that the Controller * generates. * <li> Invoke all Controller methods on the Controller. * <li> For all asynchronous methods (realize, prefetch) a completion * event will not be generated until all added Controllers have * generated completion events. * </ul><p> * * <b>Note:</b> It is undefined what will happen if a Controller is * under the control of a Player and any of the * Controller's methods are called outside of the controlling * Player. * * @param newController the Controller this * Player will control. * @exception IncompatibleTimeBaseException thrown if the new controller * will not accept the player's timebase. */ public synchronized void addController(Controller newController) throws IncompatibleTimeBaseException { int playerState = getState(); if (playerState == Started) { throwError(new ClockStartedError("Cannot add controller to a started player")); } if ( (playerState == Unrealized) || (playerState == Realizing) ) { throwError(new NotRealizedError("A Controller cannot be added to an Unrealized Player")); } if (newController == null || newController == this) return; int controllerState = newController.getState(); if ( (controllerState == Unrealized) || (controllerState == Realizing) ) { throwError(new NotRealizedError("An Unrealized Controller cannot be added to a Player")); } if (controllerList.contains(newController)) { return; } if (playerState == Prefetched) { if ( (controllerState == Realized) || (controllerState == Prefetching) ) { // System.out.println("Calling deallocate"); deallocate(); // Transition back to realized state } } manageController(newController); // Synchronize the players. newController.setTimeBase(getTimeBase()); newController.setMediaTime(getMediaTime()); newController.setStopTime(getStopTime()); if (newController.setRate(getRate()) != getRate()) { // The slave does not support the master's rate. // We'll reset everything back to rate 1.0. setRate(1.0f); } } /** * Stop controlling a Controller. * * @param oldController the Controller to stop controlling. */ public final synchronized void removeController(Controller oldController) { int state = getState(); if (state < Realized) { throwError(new NotRealizedError("Cannot remove controller from a unrealized player")); } if (state == Started) { throwError(new ClockStartedError("Cannot remove controller from a started player")); } if (oldController == null) return; if (controllerList.contains(oldController)) { controllerList.removeElement(oldController); oldController.removeControllerListener(this); updateDuration(); // Reset the controller to its default time base. try { oldController.setTimeBase(null); } catch (IncompatibleTimeBaseException e) {} } } /** * Return true if the player is currently playing media * with an audio track. * @return true if the player is playing audio. */ protected abstract boolean audioEnabled(); /** * Return true if the player is currently playing media * with a video track. * @return true if the player is playing video. */ protected abstract boolean videoEnabled(); /** * This should be implemented by the subclass. * The subclass method should return the master TimeBase -- the * TimeBase that all other controllers slave to. * Use SystemTimeBase if unsure. * @return the master time base. */ protected abstract TimeBase getMasterTimeBase(); /** * The stub function (invoked from configure()) to perform the steps to * configure the player. It performs the following: * <ul> * <li> call configure() on each controller managed by this player. * <li> wait for ConfigureCompleteEvent from each controller; * </ul> * Subclasses are allowed to override doConfigure(). But this should be * done in caution. Subclass should also invoke super.doConfigure(). * This is called from a separately running thread. * @return true if successful. */ protected synchronized boolean doConfigure() { potentialEventsList = configureEventList; // List of potential events for the // configure() method resetReceivedEventList(); // Reset list of received events receivedAllEvents = false; currentControllerList.removeAllElements(); int i = controllerList.size(); while (--i >= 0) { Controller c = (Controller) controllerList.elementAt(i); if (c.getState() == Unrealized && (c instanceof Processor || c instanceof BasicController)) { currentControllerList.addElement(c); } } i = currentControllerList.size(); while (--i >= 0) { Controller c = (Controller) currentControllerList.elementAt(i); if (c instanceof Processor) ((Processor)c).configure(); else if (c instanceof BasicController) ((BasicController)c).configure(); } if (!currentControllerList.isEmpty()) { try { while (!closing && !receivedAllEvents) wait(); } catch (InterruptedException e) { } currentControllerList.removeAllElements(); } // Make sure all the controllers are in in Configured State. // If not, it means that configure failed on one or more controllers. // Currenly, if configure fails then you get a ResourceUnavailableEvent // instead of RealizeCompleteEvent i = controllerList.size(); while (--i >= 0) { Controller c = (Controller) controllerList.elementAt(i); if ((c instanceof Processor || c instanceof BasicController) && c.getState() < Configured) { Log.error("Error: Unable to configure " + c); source.disconnect(); return false; } } // subclass will implement this to configure up the signal graph. return true; } /** * Called as a last step to complete the configure call. */ protected void completeConfigure() { super.completeConfigure(); synchronized (this) { notify(); } } /** * Called when configure fails. */ protected void doFailedConfigure() { super.doFailedConfigure(); synchronized (this) { notify(); } close(); } /** * The stub function (invoked from configure()) to perform the steps to * configure the player. It performs the following: * <ul> * <li> call realize() on each controller managed by this player. * <li> wait for RealizeCompleteEvent from each controller; * </ul> * Subclasses are allowed to override doRealize(). But this should be * done in caution. Subclass should also invoke super.doRealize(). * This is called from a separately running thread. * @return true if successful. */ protected synchronized boolean doRealize() { potentialEventsList = realizeEventList; // List of potential events for the // realize() method resetReceivedEventList(); // Reset list of received events receivedAllEvents = false; currentControllerList.removeAllElements(); int i = controllerList.size(); while (--i >= 0) { Controller c = (Controller) controllerList.elementAt(i); if (c.getState() == Unrealized || c.getState() == Configured) { currentControllerList.addElement(c); } } i = currentControllerList.size(); while (--i >= 0) { Controller c = (Controller) currentControllerList.elementAt(i); c.realize(); } if (!currentControllerList.isEmpty()) { try { while (!closing && !receivedAllEvents) wait(); } catch (InterruptedException e) { } currentControllerList.removeAllElements(); } // Make sure all the controllers are in in Realized State. // If not, it means that realize failed on one or more controllers. // Currenly, if realize fails then you get a ResourceUnavailableEvent // instead of RealizeCompleteEvent i = controllerList.size(); while (--i >= 0) { Controller c = (Controller) controllerList.elementAt(i); if (c.getState() < Realized) { Log.error("Error: Unable to realize " + c); source.disconnect(); return false; } } updateDuration(); if ( /*securityPrivelege &&*/ (jmfSecurity != null) ) { String permission = null; try { if (jmfSecurity.getName().startsWith("jmf-security")) { permission = "thread"; jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD); m[0].invoke(cl[0], args[0]); permission = "thread group"; jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP); m[0].invoke(cl[0], args[0]); } else if (jmfSecurity.getName().startsWith("internet")) { PolicyEngine.checkPermission(PermissionID.THREAD); PolicyEngine.assertPermission(PermissionID.THREAD); } } catch (Exception e) { if (JMFSecurityManager.DEBUG) { System.err.println("Unable to get " + permission + " privilege " + e); } securityPrivelege = false; // TODO: Do the right thing if permissions cannot be obtained. // User should be notified via an event } } if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) { try { Constructor cons = CreateWorkThreadAction.cons; statsThread = (StatsThread) jdk12.doPrivM.invoke( jdk12.ac, new Object[] { cons.newInstance( new Object[] { StatsThread.class, BasicPlayer.class, this })}); statsThread.start(); } catch (Exception e) { } } else { statsThread = new StatsThread(this); statsThread.start(); } // subclass will implement this to connect up the signal graph. return true; } /** * Called as a last step to complete the realize call. */ protected void completeRealize() { state = Realized; try { slaveToMasterTimeBase(getMasterTimeBase()); } catch (IncompatibleTimeBaseException e) { Log.error(e); } super.completeRealize(); synchronized (this) { notify(); } } /** * Called when realize fails. */ protected void doFailedRealize() { super.doFailedRealize(); synchronized (this) { notify(); } close(); } /** * Called as a last step to complete the prefetch call. */ protected void completePrefetch() { super.completePrefetch(); synchronized(this) { notify(); } } /** * Called when prefetch fails. */ protected void doFailedPrefetch() { super.doFailedPrefetch(); synchronized (this) { notify(); } } /** * Called when the realize() is aborted, i.e. deallocate() was called * while realizing. Release all resources claimed previously by the * realize() call. */ protected final void abortRealize() { if (controllerList != null) { int i = controllerList.size(); while (--i >= 0) { Controller c = (Controller) controllerList.elementAt(i); c.deallocate(); } } synchronized(this) { notify(); } } /** * The stub function to perform the steps to prefetch the controller. * This will call prefetch() on every controller in the controller list and wait * for their completion events. * This is called from a separately running thread. * @return true if successful. */ protected /*synchronized*/ boolean doPrefetch() { potentialEventsList = prefetchEventList; // List of potential events for the // prefetch() method resetReceivedEventList(); // Reset list of received events receivedAllEvents = false; currentControllerList.removeAllElements(); Vector list = controllerList; if (list == null) { return false; } int i = list.size(); while (--i >= 0) { Controller c = (Controller) list.elementAt(i); if (c.getState() == Realized) { currentControllerList.addElement(c); c.prefetch(); } } if (!currentControllerList.isEmpty()) { synchronized(this) { try { while (!closing && !receivedAllEvents) wait(); } catch (InterruptedException e) { } currentControllerList.removeAllElements(); } } // Make sure all the controllers are in in Prefetched State. // If not, it means that prefetch failed on one or more controllers. // Currenly, if prefetch fails then you get a ResourceUnavailableEvent // instead of PrefetchCompleteEvent i = list.size(); while (--i >= 0) { Controller c = (Controller) list.elementAt(i); if (c.getState() < Prefetched) { Log.error("Error: Unable to prefetch " + c + "\n"); if (optionalControllerList.contains(c)) { //System.out.println(c + " Controller is optional... continuing"); removedControllerList.addElement(c); } else { // Notify the play thread which could still be waiting. synchronized (this) { prefetchFailed = true; notifyAll(); } return false; } } } if (removedControllerList != null) { i = removedControllerList.size(); while (--i >= 0) { Object o = removedControllerList.elementAt(i); controllerList.removeElement(o); ( (BasicController) o).close(); if (! deviceBusy((BasicController) o)) { // Notify the play thread which could still be waiting. synchronized (this) { prefetchFailed = true; notifyAll(); } return false; // prefetch failed } } removedControllerList.removeAllElements(); //$ System.err.println("final list of controllers: " + list); } return true; } /** * Called when the prefetch() is aborted, i.e. deallocate() was called * while prefetching. Release all resources claimed previously by the * prefetch call. */ protected final void abortPrefetch() { if (controllerList != null) { int i = controllerList.size(); while (--i >= 0) { Controller c = (Controller) controllerList.elementAt(i); c.deallocate(); } } synchronized(this) { notify(); } } /** * Check the given controller to see if it's busy or not. * Needs to be overridden by subclass. * The subclass method should change the master timebase * if necessary. It should handle audio only or video * only tracks properly when the device is busy. * @return true if the given controller is usable; false if the controller * cannot be used. */ protected boolean deviceBusy(BasicController mc) { return true; } /** * Slave all the controllers to the master time base. * The controllers should be in realized or greater state * This differs from the setTimeBase() as it loops through each * controllers and call setTimeBase on each of them. * @param tb the time base to be used by all controllers. * @exception IncompatibleTimeBaseException thrown if any controller * will not accept the player's timebase. */ protected void slaveToMasterTimeBase(TimeBase tb) throws javax.media.IncompatibleTimeBaseException { //$$ System.out.println("slaveToMasterTimeBase: master timebase is " + tb); //$ System.out.println("Setting master " + tb + " on " + this); this.setTimeBase(tb); // For the player } private /*synchronized*/ void notifyIfAllEventsArrived(Vector controllerList, Vector receivedEventList) { if ( (receivedEventList != null) && (receivedEventList.size() == currentControllerList.size()) ) { receivedAllEvents = true; resetReceivedEventList(); // Reset list of received events synchronized(this) { notifyAll(); } } } protected /*synchronized*/ void processEvent(ControllerEvent evt) { Controller source = evt.getSourceController(); if (evt instanceof AudioDeviceUnavailableEvent) { sendEvent(new AudioDeviceUnavailableEvent(this)); return; } // If this is a closed event triggered by one of the // managed controllers, not triggered by the player, // then we'll need to programmtically close all the // controllers and the player itself. if ( evt instanceof ControllerClosedEvent && !closing && controllerList.contains(source) && ! (evt instanceof ResourceUnavailableEvent) ) { // The source of the error event should have been closed // already. So we'll just remove it from the list of // managed controllers. controllerList.removeElement(source); if (evt instanceof ControllerErrorEvent) sendEvent(new ControllerErrorEvent(this, ((ControllerErrorEvent) evt).getMessage())); close(); } // // Send SizeChangeEvent down to Player // if ( (evt instanceof SizeChangeEvent) && controllerList.contains(source) ) { // System.err.println("width = " + ((SizeChangeEvent)evt).getWidth()); // System.err.println("height = " + ((SizeChangeEvent)evt).getHeight()); sendEvent(new SizeChangeEvent(this, ((SizeChangeEvent)evt).getWidth(), ((SizeChangeEvent)evt).getHeight(), ((SizeChangeEvent)evt).getScale())); return; } // // Send UnsupportedFormatEvent down to Player // /* if ( (evt instanceof UnsupportedFormatEvent) && controllerList.contains(source) ) { // System.err.println("Reason = " + ((UnsupportedFormatEvent)evt).toString()); sendEvent(new UnsupportedFormatEvent(this, ((UnsupportedFormatEvent)evt).getFormat())); return; } */ // If we get a DurationUpdateEvent from one of the controllers, // update the duration of the player if ((evt instanceof DurationUpdateEvent) && controllerList.contains(source)) { updateDuration(); return; } // HANGS. // if ((evt instanceof RestartingEvent) && controllerList.contains(source)) { // System.out.println("MP: Got RestartingEvent from " + source); // stop(LOCAL_STOP); // Stop without sending any stop event // sendEvent(new RestartingEvent(this, Started, Prefetching, Started, // getMediaTime())); // } // So I am handling RestartingEvent this way if ((evt instanceof RestartingEvent) && controllerList.contains(source)) { restartFrom = source; int i = controllerList.size(); super.stop(); // Added setTargetState(Prefetched); // necessary even if super.stop is called. for (int ii = 0; ii < i; ii++) { Controller c = (Controller) controllerList.elementAt(ii); if (c != source) { c.stop(); } } super.stop(); // doStop(); // Allow subclasses to extend the behavior sendEvent(new RestartingEvent(this, Started, Prefetching, Started, getMediaTime())); } if ((evt instanceof StartEvent) && (source == restartFrom) ) { restartFrom = null; // $$ TODO: Should probably send PrefetchCompleteEvent start(); } if ( (evt instanceof SeekFailedEvent) && controllerList.contains(source) ) { int i = controllerList.size(); super.stop(); // Added setTargetState(Prefetched); // necessary even if super.stop is called. for (int ii = 0; ii < i; ii++) { Controller c = (Controller) controllerList.elementAt(ii); if (c != source) { c.stop(); } } /* super.stop(); setMediaTime(new Time(0)); start(); */ sendEvent(new SeekFailedEvent(this, Started, Prefetched, Prefetched, getMediaTime())); } if ( (evt instanceof EndOfMediaEvent) && controllerList.contains(source) ) { if (eomEventsReceivedFrom.contains(source)) { return; } eomEventsReceivedFrom.addElement(source); if (eomEventsReceivedFrom.size() == controllerList.size()) { super.stop(); sendEvent(new EndOfMediaEvent(this, Started, Prefetched, getTargetState(), getMediaTime())); } return; } if ((evt instanceof StopAtTimeEvent) && controllerList.contains(source) && (getState() == Started)) { synchronized (stopAtTimeReceivedFrom) { if (stopAtTimeReceivedFrom.contains(source)) return; stopAtTimeReceivedFrom.addElement(source); boolean allStopped = (stopAtTimeReceivedFrom.size() == controllerList.size()); if (!allStopped) { // Now check if the other controllers have already EOM'ed. allStopped = true; for (int i = 0; i < controllerList.size(); i++) { Controller c = (Controller)controllerList.elementAt(i); if (!stopAtTimeReceivedFrom.contains(c) && !eomEventsReceivedFrom.contains(c)) { allStopped = false; break; } } } if (allStopped) { super.stop(); doSetStopTime(Clock.RESET); sendEvent(new StopAtTimeEvent(this, Started, Prefetched, getTargetState(), getMediaTime())); } return; } // synchronized stopAtTimeReceivedFrom } if ((evt instanceof CachingControlEvent) && controllerList.contains(source) ) { CachingControl mcc = (CachingControl) ((CachingControlEvent) evt).getCachingControl(); sendEvent(new CachingControlEvent(this, mcc, mcc.getContentProgress())); return; } Vector eventList = potentialEventsList; if (controllerList != null && controllerList.contains(source) && eventList != null && eventList.contains(evt.getClass().getName())) { updateReceivedEventsList(evt); notifyIfAllEventsArrived(controllerList, getReceivedEventsList()); } } private boolean trySetRate(float rate) { int i = controllerList.size(); while(--i >=0) { Controller c = (Controller)controllerList.elementAt(i); if ( c.setRate(rate) != rate ) { return false; } } return true; } protected float doSetRate(float factor) { return factor; } /** * Set the playback rate on the player. * It loops through its list of controllers and invoke setRate on each * of them. */ public float setRate(float rate) { if (state < Realized) { throwError(new NotRealizedError("Cannot set rate on an unrealized Player.")); } // Verify to see if the DataSource supports that rate. if (source instanceof RateConfigureable) rate = checkRateConfig((RateConfigureable)source, rate); float oldRate = getRate(); if (oldRate == rate) return rate; if (getState() == Controller.Started) { aboutToRestart = true; stop(RESTARTING); } float rateSet; // Actual rate set if (!trySetRate(rate)) { if (!trySetRate(oldRate)) { // try to go back to the oldRate trySetRate(1.0F); // try setRate(1.0) which shouldn't fail rateSet = 1.0F; } else { rateSet = oldRate; } } else { rateSet = rate; } super.setRate(rateSet); if (aboutToRestart) { syncStart(getTimeBase().getTime()); aboutToRestart = false; } return rateSet; } /** * Check if the given rate configureable supports the given rate. * if not, returns the closest match. */ float checkRateConfig(RateConfigureable rc, float rate) { RateConfiguration config[] = rc.getRateConfigurations(); if (config == null) return 1.0f; RateConfiguration c; RateRange rr; float corrected = 1.0f; for (int i = 0; i < config.length; i++) { rr = config[i].getRate(); if (rr != null && rr.inRange(rate)) { rr.setCurrentRate(rate); corrected = rate; c = rc.setRateConfiguration(config[i]); if (c != null && (rr = c.getRate()) != null) corrected = rr.getCurrentRate(); break; } } return corrected; } /** * This is being called from a looping thread to update the stats. */ abstract public void updateStats(); /************************************************************************* * INNER CLASSES *************************************************************************/ } // PlayThread and StatsThread are no longer inner classes class PlayThread extends MediaThread { BasicPlayer player; public PlayThread(BasicPlayer player) { this.player = player; setName(getName() + " (PlayThread)"); useControlPriority(); } public void run() { player.play(); } } class StatsThread extends LoopThread { BasicPlayer player; int pausecount = -1; public StatsThread(BasicPlayer p) { this.player = p; } protected boolean process() { try { Thread.currentThread().sleep(1000); } catch (Exception e) {} // Check to see if the thread was killed. // If so exits. if (!waitHereIfPaused()) return false; if ( player.getState() == Controller.Started ) { pausecount = -1; player.updateStats(); } else if ( pausecount < 5 ) { pausecount++; player.updateStats(); } return true; } }