/* * @(#)AbstractPlayer.java 3.1 2011-02-17 * * Copyright (c) 1999-2011 Werner Randelshofer, Goldau, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package org.monte.media; import org.monte.media.concurrent.SequentialDispatcher; import java.beans.*; import javax.swing.event.*; /** * Generic interface for media players. * * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland * @version 3.1 2011-02-17 Merged with CubeTwister. * <br>2.1 2009-12-25 Added support for color cycling. * <br>2.0.1 2009-11-23 Removed unused imports. * <br>2.0 2005-07-09 Uses now a SequentialDispatcher for multithreading * instead of individual threads. The SequentialDispatcher guarantees, that all * animation steps are performed sequentially. This removes the need for clients * to wait until a specific state has been reached, before they can request * another state from the player. * <br>1.1 2002-02-06 Support for ChangeListeners added. * <br>1.0 1999-10-19 */ public abstract class AbstractPlayer implements Player, Runnable { /** * Current state of the player. * Note: Only method run() may change the value of * this variable. */ private int state = UNREALIZED; /** * Target state of the player. */ private int targetState = UNREALIZED; /** * Listener support. */ protected EventListenerList listenerList = new EventListenerList(); /** Support for property change listeners. */ protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** * The dispatcher. */ protected SequentialDispatcher dispatcher = new SequentialDispatcher(); /** * Creates a new instance. */ public AbstractPlayer() { } /** * Gets the current state of the player. */ @Override public int getState() { return state; } /** * Gets the target state. */ @Override public int getTargetState() { return targetState; } /** * Sets the desired target state. */ @Override public void setTargetState(final int state) { synchronized (this) { if (targetState != CLOSED) { targetState = state; AbstractPlayer.this.notifyAll(); dispatcher.dispatch(this); } } } /** * Initiates the following asynchronous * state transitions: * unrealized -> realizing -> realized * realizing -> realized * realized * started -> throws IllegalStateException * closed -> throws IllegalStateException */ @Override public void realize() { switch (getState()) { case CLOSED : throw new IllegalStateException("Realize closed player."); // break; not reached case STARTED : throw new IllegalStateException("Realize started player."); // break; not reached } setTargetState(REALIZED); } /** * Initiates the following asynchronous * state transitions: * unrealized -> realizing -> realized -> prefetching -> prefetched * realizing -> realized -> prefetching -> prefetched * realized -> prefetching -> prefetched * prefetching -> prefetched * prefetched * started -> throws IllegalStateException * closed -> throws IllegalStateException */ public void prefetch() { switch (getState()) { case CLOSED : throw new IllegalStateException("Prefetch closed player."); // break; not reached case STARTED : throw new IllegalStateException("Prefetch started player."); // break; not reached } setTargetState(PREFETCHED); } /** * Initiates the following asynchronous * state transitions: * realizing -> unrealized * prefetching -> realized * prefetched -> realized * realized * started -> throws IllegalStateException * closed -> throws IllegalStateException */ public void deallocate() { switch (getState()) { case CLOSED : throw new IllegalStateException("Deallocate closed player."); // break; not reached case REALIZING : setTargetState(UNREALIZED); break; case PREFETCHING : setTargetState(REALIZED); break; case PREFETCHED : setTargetState(REALIZED); break; case STARTED : throw new IllegalStateException("Deallocate started player."); // break; not reached } } /** * Initiates the following asynchronous * state transitions: * unrealized -> realizing -> realized -> prefetching -> prefetched -> started * realizing -> realized -> prefetching -> prefetched -> started * realized -> prefetching -> prefetched -> started * prefetching -> prefetched -> started * prefetched -> started * started * closed -> throws IllegalStateException */ public void start() { switch (getState()) { case CLOSED : throw new IllegalStateException("Can't start closed player."); // break; not reached } setTargetState(STARTED); } /** * Initiates the following asynchronous * state transitions: * started -> prefetched * unrealized * realizing * prefetching * prefetched * closed -> throws IllegalStateException */ public void stop() { switch (getState()) { case CLOSED : //throw new IllegalStateException("Stop closed player."); //allow stop on closed player break; case STARTED : setTargetState(PREFETCHED); break; } } /** * Initiates the following asynchronous * state transitions: * any state -> closed */ public void close() { setTargetState(CLOSED); } /** * Adds a listener that wants to be notified about * state changes of the player. */ public void addStateListener(StateListener l) { listenerList.add(StateListener.class, l); } /** * Removes a listener. */ public void removeStateListener(StateListener l) { listenerList.remove(StateListener.class, l); } /** * Adds a listener who is interested in changes of this object. */ public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } /** * Removes a previously registered listener. */ public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } /** * Notifies all registered state listeners and * all registered change listeners. */ protected void fireStateChanged(int newState) { StateEvent stateEvent = null; ChangeEvent changeEvent = null; Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -=2 ) { if (listeners[i] == StateListener.class) { // lazily create the event object if (stateEvent == null) stateEvent = new StateEvent(this, newState); ((StateListener)listeners[i+1]).stateChanged(stateEvent); } if (listeners[i] == ChangeListener.class) { // lazily create the event object if (changeEvent == null) changeEvent = new ChangeEvent(this); ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); } } } /** * Notifies all registered change listeners. */ protected void fireStateChanged() { StateEvent stateEvent = null; ChangeEvent changeEvent = null; Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -=2 ) { if (listeners[i] == ChangeListener.class) { // lazily create the event object if (changeEvent == null) changeEvent = new ChangeEvent(this); ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); } } } /** Most of the real work goes here. We have to decide when * to post events like EndOfMediaEvent and StopAtTimeEvent * and TimeLineEvent. */ public void run() { while (state != targetState) { if (targetState > state) { state++; } else { state = targetState; } fireStateChanged(state); switch (state) { case CLOSED : doClosed(); break; case UNREALIZED : doUnrealized(); break; case REALIZING : doRealizing(); break; case REALIZED : doRealized(); break; case PREFETCHING : doPrefetching(); break; case PREFETCHED : doPrefetched(); break; case STARTED : doStarted(); setTargetState(PREFETCHED); break; } } } /** * Does the work for the closed state. */ abstract protected void doClosed(); /** * Does the work for the unrealized state. */ abstract protected void doUnrealized(); /** * Does the work for the realizing state. */ abstract protected void doRealizing(); /** * Does the work for the realized state. */ abstract protected void doRealized(); /** * Does the work for the prefetching state. */ abstract protected void doPrefetching(); /** * Does the work for the prefetched state. */ abstract protected void doPrefetched(); /** * Does the work for the started state. */ abstract protected void doStarted(); /** * Adds a listener that wants to be notified about * state changes of the player. */ public void addChangeListener(ChangeListener listener) { listenerList.add(ChangeListener.class, listener); } /** * Removes a listener. */ public void removeChangeListener(ChangeListener listener) { listenerList.remove(ChangeListener.class, listener); } /** * Returns true when the target state of the player is equal to STARTED. */ public boolean isActive() { return getTargetState() == STARTED; } }