0001: /*
0002: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0004: *
0005: * This program is free software; you can redistribute it and/or
0006: * modify it under the terms of the GNU General Public License version
0007: * 2 only, as published by the Free Software Foundation.
0008: *
0009: * This program is distributed in the hope that it will be useful, but
0010: * WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * General Public License version 2 for more details (a copy is
0013: * included at /legal/license.txt).
0014: *
0015: * You should have received a copy of the GNU General Public License
0016: * version 2 along with this work; if not, write to the Free Software
0017: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0018: * 02110-1301 USA
0019: *
0020: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0021: * Clara, CA 95054 or visit www.sun.com if you need additional
0022: * information or have any questions.
0023: */
0024: package com.sun.mmedia;
0025:
0026: import java.util.Vector;
0027: import java.util.Enumeration;
0028: import java.util.Hashtable;
0029:
0030: import java.io.*;
0031:
0032: import javax.microedition.media.*;
0033: import javax.microedition.media.control.*;
0034:
0035: //import com.sun.mmedia.PermissionAccessor;
0036:
0037: /**
0038: * BasicPlayer provides basic implementation for the Player methods.
0039: * Many of the methods call do<method> to do the actual work that can
0040: * be overridden by subclasses.
0041: *
0042: * @created January 13, 2005
0043: */
0044: public abstract class ABBBasicPlayer implements Player {
0045:
0046: /**
0047: * global player id
0048: */
0049: private static int pcount = -1;
0050:
0051: /**
0052: * lock object
0053: */
0054: private static Object idLock = new Object();
0055:
0056: /**
0057: * the state of this player
0058: */
0059: public int state = UNREALIZED;
0060:
0061: /**
0062: * the loopCount of this player
0063: */
0064: int loopCountSet = 1, loopCount;
0065:
0066: /**
0067: * the flag to indicate whether the Player is currently paused at EOM.
0068: * If true, the Player will seek back to the beginning when
0069: * restarted.
0070: */
0071: boolean EOM = false;
0072:
0073: /**
0074: * the flag to indicate looping after EOM.
0075: */
0076: boolean loopAfterEOM = false;
0077:
0078: /**
0079: * this player's playerlisteners
0080: */
0081: Vector listeners = new Vector(2);
0082:
0083: /**
0084: * flag shows that "listeners" have been modified while
0085: * player is executing callbacks from "listeners".
0086: */
0087: boolean listenersModified = false;
0088:
0089: /**
0090: * Asynchronous event mechanism.
0091: */
0092: PlayerEventQueue eventQueue = null;
0093:
0094: /**
0095: * event queue lock obj
0096: */
0097: Object evtLock = new Object();
0098:
0099: /**
0100: * player ID of this player
0101: */
0102: protected int pID = 0;
0103:
0104: /**
0105: * hastable to map playerID to instances
0106: */
0107: private static Hashtable mplayers = new Hashtable(4);
0108:
0109: /**
0110: * table of player states
0111: */
0112: private static Hashtable pstates = new Hashtable();
0113:
0114: /**
0115: * table of media times
0116: */
0117: private static Hashtable mtimes = new Hashtable();
0118:
0119: /**
0120: * Control package name
0121: */
0122: protected final static String pkgName = "javax.microedition.media.control.";
0123:
0124: /**
0125: * Centralized control management with string constants for each
0126: * implemented Control.
0127: * <p>
0128: * For adding a new control interfaces, follow the following steps:
0129: * <ol>
0130: * <li>Add new control name here. If it is not in the package
0131: * javax.microedition.media.control, then the full package
0132: * name of the control must be used.
0133: * <li>Add the control's name field to the allCtrls array (see below)
0134: * </ol>
0135: * <p>
0136: * Size note: it would be space saving to declare the array allCtrls
0137: * with the strings directly and not define the strings constants here.
0138: * However, it is more convenient for descendants to use
0139: * these constants, instead of e.g.
0140: * <code> allCtrls[4]; // RateControl</code>
0141: *
0142: * @see #getControls()
0143: * @see #doGetControl(String)
0144: * @see #allCtrls
0145: */
0146:
0147: /**
0148: * Description of the Field
0149: */
0150: protected final static String fpcName = "FramePositioningControl";
0151: /**
0152: * Description of the Field
0153: */
0154: protected final static String guiName = "GUIControl";
0155: /**
0156: * Description of the Field
0157: */
0158: protected final static String mdcName = "MetaDataControl";
0159: /**
0160: * Description of the Field
0161: */
0162: protected final static String micName = "MIDIControl";
0163: /**
0164: * Description of the Field
0165: */
0166: protected final static String picName = "PitchControl";
0167: /**
0168: * Description of the Field
0169: */
0170: protected final static String racName = "RateControl";
0171: /**
0172: * Description of the Field
0173: */
0174: protected final static String recName = "RecordControl";
0175: /**
0176: * Description of the Field
0177: */
0178: protected final static String stcName = "StopTimeControl";
0179: /**
0180: * Description of the Field
0181: */
0182: protected final static String tecName = "TempoControl";
0183: /**
0184: * Description of the Field
0185: */
0186: protected final static String tocName = "ToneControl";
0187: /**
0188: * Description of the Field
0189: */
0190: protected final static String vicName = "VideoControl";
0191: /**
0192: * Description of the Field
0193: */
0194: protected final static String vocName = "VolumeControl";
0195: /**
0196: * This one is not among public JSR135 controls,
0197: * seems that this is used only for RTSP PLayer.
0198: * But its participation in "search-by-name" slows down
0199: * all players that use this array for "getControls()".
0200: */
0201: protected final static String rtspName = "RtspControl";
0202:
0203: /**
0204: * An array containing all available controls in Players
0205: * extending BasicPlayer.
0206: * A player can overwrite this array in order to change the list.
0207: */
0208: private static final String[] allCtrls = { fpcName, /*FramePositioningControl*/
0209: guiName, /*GUIControl*/
0210: mdcName, /*MetaDataControl*/
0211: micName, /*MIDIControl*/
0212: picName, /*PitchControl*/
0213: racName, /*RateControl*/
0214: recName, /*RecordControl*/
0215: stcName, /*StopTimeControl*/
0216: tecName, /*TempoControl*/
0217: tocName, /*ToneControl*/
0218: vicName, /*VideoControl*/
0219: vocName, /*VolumeControl*/
0220: rtspName, /*(non-standard) RtspControl*/
0221: };
0222:
0223: /**
0224: * An array containing all needed permissions in Players
0225: * extending BasicPlayer.
0226: * A player can overwrite this array in order to change the list.
0227: *
0228: * By default it is empty.
0229: */
0230: private static final int[] allPermissions = {};
0231:
0232: /**
0233: * is set by checkPermissions() to bypass further checks
0234: */
0235: private boolean isTrusted;
0236:
0237: /**
0238: * array of controls for a given player
0239: */
0240: private String control_names[];
0241:
0242: /**
0243: * array of permissions for a given player
0244: */
0245: private int permissions[];
0246:
0247: /**
0248: * the default size of the event queue
0249: * can be overridden by descendants
0250: */
0251: int eventQueueSize = 20;
0252:
0253: /**
0254: * flag to prevent delivering events after the CLOSED event
0255: */
0256: private boolean closedDelivered;
0257:
0258: /**
0259: * listener for media events while the midlet is in paused state.
0260: */
0261: private static MIDletPauseListener pauseListener = null;
0262: private static boolean vmPaused = false;
0263:
0264: /* Source data for player */
0265: InputStream source;
0266:
0267: /**
0268: * Sets the listener for media activity notifications.
0269: *
0270: * This interface can only be set once and shall only be used
0271: * by MIDletState.
0272: */
0273: public static void setMIDletPauseListener(
0274: MIDletPauseListener listener) {
0275: //System.out.println("DEBUG: about to BP.setMIDletPauseListener(" + listener + ")");
0276: // Only set the listener once!
0277: // If the listener is aleady set then return witout waring.
0278: if (pauseListener == null) {
0279: pauseListener = listener;
0280: }
0281: }
0282:
0283: /**
0284: * Informs the BasicPlayer that the VM has been paused if
0285: * paused is set to true - or resumed if paused is set to false
0286: */
0287: public static void pauseStateEntered(MIDletPauseListener listener,
0288: boolean paused) {
0289: //System.out.println("DEBUG: about to BP.pauseStateEntered(" + listener + "," + paused + ")");
0290: // if the listeners don't match then simply return
0291: if (listener != pauseListener)
0292: return;
0293:
0294: vmPaused = paused;
0295:
0296: if (vmPaused) {
0297: for (Enumeration e = mplayers.elements(); e
0298: .hasMoreElements();) {
0299: ABBBasicPlayer p = (ABBBasicPlayer) e.nextElement();
0300:
0301: if (p.getState() == STARTED) {
0302: notifyPauseListener("Player");
0303: }
0304: }
0305: /*pauseAll();
0306: } else {
0307: resumeAll();*/
0308: }
0309: }
0310:
0311: public static void notifyPauseListener(String msg) {
0312: if (vmPaused && pauseListener != null) {
0313: pauseListener.reportActivity(msg);
0314: }
0315: }
0316:
0317: /**
0318: *Constructor for the ABBBasicPlayer object
0319: */
0320: public ABBBasicPlayer() {
0321: init();
0322: control_names = allCtrls;
0323: permissions = allPermissions;
0324: }
0325:
0326: protected ABBBasicPlayer(String[] n, int[] p) {
0327: init();
0328: control_names = (n == null) ? allCtrls : n;
0329: permissions = (p == null) ? allPermissions : p;
0330: }
0331:
0332: private void init() {
0333:
0334: synchronized (idLock) {
0335: pcount = (pcount + 1) % 32767;
0336: pID = pcount;
0337: }
0338: mplayers.put(new Integer(pID), this );
0339: }
0340:
0341: /**
0342: * Initializes Player by Media Encodings obtained from URI and parsed.
0343: * To be called by Manager when new player (from URI) is being created.
0344: *
0345: * @param encodings media encodings in form "key=value", separated by '&'.
0346: *
0347: * @returns true if initialization was successful, false otherwise
0348: */
0349: public boolean initFromURL(String encodings) {
0350: return true;
0351: }
0352:
0353: /**
0354: * Checks if user application has all permissions needed to access player
0355: */
0356: protected final void checkPermissions() throws SecurityException {
0357: /*
0358: if (isTrusted) return;
0359: //TBD: check locator-specific permissions ?
0360: for (int i = 0; i < permissions.length; ++i)
0361: PermissionAccessor.checkPermissions(permissions[i]);
0362: isTrusted = true;
0363: */
0364: }
0365:
0366: /**
0367: * Check to see if the Player is closed. If the
0368: * unrealized boolean flag is true, check also to
0369: * see if the Player is UNREALIZED.
0370: *
0371: * @param unrealized Description of the Parameter
0372: */
0373: protected final void chkClosed(boolean unrealized) {
0374: /*
0375: * This method is indended to be called from synchronized methods
0376: * (that change player's state), but in fact
0377: * it is invoked from unsynchronized methods too,
0378: * so, as a temporary solution,
0379: * it shall eliminate access to player's state:
0380: * it must get the state only once and then work with a local variable.
0381: */
0382: int theState = this .state;
0383: if (theState == CLOSED
0384: || (unrealized && theState == UNREALIZED)) {
0385: throw new IllegalStateException(
0386: "Can't invoke the method at the "
0387: + (theState == CLOSED ? "closed"
0388: : "unrealized") + " state ");
0389: }
0390: }
0391:
0392: // JAVADOC COMMENT ELIDED
0393: public synchronized void setLoopCount(int count) {
0394: //System.out.println("DEBUG: about to BP.setLoopCount(" + count + ") for player=" + this);
0395: chkClosed(false);
0396:
0397: if (state == STARTED) {
0398: throw new IllegalStateException("setLoopCount");
0399: }
0400:
0401: if (count == 0 || count < -1) {
0402: throw new IllegalArgumentException("setLoopCount");
0403: }
0404:
0405: loopCountSet = count;
0406: loopCount = count;
0407:
0408: doSetLoopCount(count);
0409: }
0410:
0411: /**
0412: * Description of the Method
0413: *
0414: * @param count Description of the Parameter
0415: */
0416: protected void doSetLoopCount(int count) {
0417: }
0418:
0419: public static final int AUDIO_NONE = 0;
0420: public static final int AUDIO_PCM = 1;
0421: public static final int AUDIO_MIDI = 2;
0422:
0423: public int getAudioType() {
0424: return AUDIO_NONE;
0425: }
0426:
0427: public void setOutput(Object output) {
0428: }
0429:
0430: public Object getOutput() {
0431: return null;
0432: }
0433:
0434: public void setSource(InputStream source) throws IOException,
0435: MediaException {
0436: this .source = source;
0437: }
0438:
0439: // JAVADOC COMMENT ELIDED
0440: public synchronized void realize() throws MediaException {
0441: //System.out.println("DEBUG: about to BP.realize() for player=" + this);
0442: chkClosed(false);
0443:
0444: if (state >= REALIZED) {
0445: return;
0446: }
0447:
0448: doRealize();
0449:
0450: state = REALIZED;
0451: }
0452:
0453: /**
0454: * Subclasses need to implement this to realize
0455: * the <code>Player</code>.
0456: *
0457: * @exception MediaException Description of the Exception
0458: */
0459: protected abstract void doRealize() throws MediaException;
0460:
0461: // JAVADOC COMMENT ELIDED
0462: public synchronized void prefetch() throws MediaException {
0463: //System.out.println("DEBUG: about to BP.prefetch() for player=" + this);
0464: chkClosed(false);
0465:
0466: if (state >= PREFETCHED) {
0467: return;
0468: }
0469:
0470: if (state < REALIZED) {
0471: realize();
0472: } else {
0473: //if realize has been called the permission will be checked from there
0474: checkPermissions();
0475: }
0476:
0477: doPrefetch();
0478:
0479: state = PREFETCHED;
0480: }
0481:
0482: /**
0483: * Subclasses need to implement this to prefetch
0484: * the <code>Player</code>.
0485: *
0486: * @exception MediaException Description of the Exception
0487: */
0488: protected abstract void doPrefetch() throws MediaException;
0489:
0490: // JAVADOC COMMENT ELIDED
0491: public synchronized void start() throws MediaException {
0492: //System.out.println("DEBUG: about to BP.start() for player=" + this + " at time=" + getMediaTime());
0493: chkClosed(false);
0494:
0495: if (state >= STARTED) {
0496: return;
0497: }
0498:
0499: if (state < PREFETCHED) {
0500: prefetch();
0501: } else {
0502: //If prefetch has been called the permission will be checked from there
0503: if (!EOM && !loopAfterEOM) {
0504: checkPermissions();
0505: }
0506: }
0507:
0508: // If it's at the EOM, it will automatically
0509: // loop back to the beginning.
0510: if (EOM)
0511: try {
0512: setMediaTime(0);
0513: } catch (MediaException me) {
0514: // Ignore, if setting media time is not supported
0515: }
0516:
0517: if (!doStart()) {
0518: throw new MediaException("start");
0519: }
0520:
0521: state = STARTED;
0522: sendEvent(PlayerListener.STARTED, new Long(getMediaTime()));
0523:
0524: // Finish any pending startup stuff in subclass
0525: // Typically used to start any threads that might potentially
0526: // generate events before the STARTED event is delivered
0527: doPostStart();
0528: //System.out.println("DEBUG: finished BP.start() for player=" + this);
0529: }
0530:
0531: /**
0532: * Subclasses need to implement this start
0533: * the <code>Player</code>.
0534: *
0535: * @return Description of the Return Value
0536: */
0537: protected abstract boolean doStart();
0538:
0539: /**
0540: * Subclasses can override this method to do the actual starting
0541: * of worker threads.
0542: */
0543: protected void doPostStart() {
0544: }
0545:
0546: // JAVADOC COMMENT ELIDED
0547: public synchronized void stop() throws MediaException {
0548: //System.out.println("DEBUG: about to BP.stop() for player=" + this + " at time=" + getMediaTime());
0549: chkClosed(false);
0550:
0551: loopAfterEOM = false;
0552:
0553: if (state < STARTED) {
0554: return;
0555: }
0556:
0557: doStop();
0558:
0559: state = PREFETCHED;
0560: sendEvent(PlayerListener.STOPPED, new Long(getMediaTime()));
0561: //System.out.println("DEBUG: finished BP.stop() for player=" + this);
0562: }
0563:
0564: /**
0565: * Subclasses need to implement this to realize
0566: * the <code>Player</code>.
0567: *
0568: * @exception MediaException Description of the Exception
0569: */
0570: protected abstract void doStop() throws MediaException;
0571:
0572: // JAVADOC COMMENT ELIDED
0573: public synchronized void deallocate() {
0574: //System.out.println("DEBUG: about to BP.deallocate() for player=" + this);
0575: chkClosed(false);
0576:
0577: loopAfterEOM = false;
0578:
0579: if (state < PREFETCHED) {
0580: return;
0581: }
0582:
0583: if (state == STARTED) {
0584: try {
0585: stop();
0586: } catch (MediaException e) {
0587: // Not much we can do here.
0588: // e.printStackTrace();
0589: }
0590: }
0591:
0592: doDeallocate();
0593:
0594: EOM = true;
0595:
0596: state = REALIZED;
0597: }
0598:
0599: /**
0600: * Subclasses need to implement this to deallocate
0601: * the <code>Player</code>.
0602: */
0603: protected abstract void doDeallocate();
0604:
0605: // JAVADOC COMMENT ELIDED
0606: public synchronized void close() {
0607: //System.out.println("DEBUG: about to BP.close() for player=" + this);
0608: if (state == CLOSED) {
0609: return;
0610: }
0611:
0612: deallocate();
0613: doClose();
0614:
0615: state = CLOSED;
0616:
0617: sendEvent(PlayerListener.CLOSED, null);
0618: mplayers.remove(new Integer(pID));
0619: }
0620:
0621: /**
0622: * Subclasses need to implement this to close
0623: * the <code>Player</code>.
0624: */
0625: protected abstract void doClose();
0626:
0627: // JAVADOC COMMENT ELIDED
0628: public synchronized long setMediaTime(long now)
0629: throws MediaException {
0630: //System.out.println("DEBUG: about to BP.setMediaTime(" + now + ") for player=" + this);
0631: chkClosed(true);
0632:
0633: long theDur = doGetDuration();
0634: if ((theDur != TIME_UNKNOWN) && (now > theDur)) {
0635: now = theDur;
0636: }
0637:
0638: long rtn = doSetMediaTime(now);
0639: EOM = false;
0640:
0641: //System.out.println("DEBUG: finished BP.setMediaTime(" + now + ")=" + rtn + " for player=" + this);
0642: return rtn;
0643: }
0644:
0645: /**
0646: * Subclasses need to implement this to set the media time
0647: * of the <code>Player</code>.
0648: *
0649: * @param now Description of the Parameter
0650: * @return Description of the Return Value
0651: * @exception MediaException Description of the Exception
0652: */
0653: protected abstract long doSetMediaTime(long now)
0654: throws MediaException;
0655:
0656: // JAVADOC COMMENT ELIDED
0657: public long getMediaTime() {
0658: //System.out.println("DEBUG: about to BP.getMediaTime() for player=" + this);
0659: chkClosed(false);
0660: return doGetMediaTime();
0661: }
0662:
0663: /**
0664: * Subclasses need to implement this to get the media time
0665: * of the <code>Player</code>
0666: *
0667: * @return Description of the Return Value
0668: */
0669: protected abstract long doGetMediaTime();
0670:
0671: // JAVADOC COMMENT ELIDED
0672: public int getState() {
0673: return state;
0674: }
0675:
0676: // JAVADOC COMMENT ELIDED
0677: public long getDuration() {
0678: //System.out.println("DEBUG: about to BP.getDuration() for player=" + this);
0679: chkClosed(false);
0680: return doGetDuration();
0681: }
0682:
0683: /**
0684: * Subclasses need to implement this to get the duration
0685: * of the <code>Player</code>.
0686: *
0687: * @return Description of the Return Value
0688: */
0689: protected abstract long doGetDuration();
0690:
0691: // JAVADOC COMMENT ELIDED
0692: public void addPlayerListener(PlayerListener playerListener) {
0693: chkClosed(false);
0694: if (playerListener != null) {
0695: /*
0696: * Excplicit "sync" is needed to raise "modified" flag.
0697: * Implicit "sync" is already inside addElemet() method,
0698: * so second sync from the same thread will do nothing ...
0699: */
0700: synchronized (listeners) {
0701: listenersModified = true;
0702: listeners.addElement(playerListener);
0703: }
0704: }
0705: }
0706:
0707: // JAVADOC COMMENT ELIDED
0708: public void removePlayerListener(PlayerListener playerListener) {
0709: chkClosed(false);
0710: if (playerListener != null) {
0711: /*
0712: * Excplicit "sync" is needed to raise "modified" flag.
0713: * Implicit "sync" is already inside removeElemet() method,
0714: * so second sync from the same thread will do nothing ...
0715: */
0716: synchronized (listeners) {
0717: listenersModified = true;
0718: listeners.removeElement(playerListener);
0719: }
0720: }
0721: }
0722:
0723: final void notifyListeners(String message, Object obj) {
0724: Object copy[];
0725: synchronized (listeners) {
0726: copy = new Object[listeners.size()];
0727: listeners.copyInto(copy);
0728: listenersModified = false;
0729: }
0730: /*
0731: * TBD: raise a flag to show that we are in callbacks
0732: * to detect re-entrance ...
0733: * (syncState object can also be used,
0734: * however it protects state changes too)
0735: */
0736: for (int i = 0; i < copy.length; i++) {
0737: PlayerListener listener = (PlayerListener) copy[i];
0738: listener.playerUpdate(this , message, obj);
0739: }
0740: /*
0741: * TBD: need to check for "listenersModified == true",
0742: * this means that one of callbacks updated listeners ->
0743: * need some actions ...
0744: */
0745: }
0746:
0747: /**
0748: * Description of the Method
0749: *
0750: * @param evtName Description of the Parameter
0751: * @param evtData Description of the Parameter
0752: */
0753: public void sendEvent(String evtName, Object evtData) {
0754: //System.out.println("DEBUG: about to BP.sendEvent(" + evtName + "," + evtData +") for player=" + this);
0755: // There's always one listener for EOM - itself (for loop procesing).
0756: // "Deliver" the CLOSED/ERROR events
0757: // so that the eventQueue thread may terminate
0758: if (listeners.size() == 0
0759: && evtName != PlayerListener.END_OF_MEDIA
0760: && evtName != PlayerListener.CLOSED
0761: && evtName != PlayerListener.ERROR) {
0762: return;
0763: }
0764:
0765: // Safeguard against sending events after CLOSED event to avoid
0766: // deadlock in event delivery thread.
0767: if (closedDelivered) {
0768: return;
0769: }
0770:
0771: // Deliver the event to the listeners.
0772: synchronized (evtLock) {
0773: if (eventQueue == null) {
0774: eventQueue = new PlayerEventQueue(this );
0775: }
0776: // TBD: attempt to ensure "eventQueue" existence
0777: // in eventQueue.sentEvent() call ...
0778: eventQueue.sendEvent(evtName, evtData);
0779: }
0780:
0781: if (evtName == PlayerListener.CLOSED
0782: || evtName == PlayerListener.ERROR) {
0783: closedDelivered = true;
0784: }
0785: }
0786:
0787: synchronized void doFinishLoopIteration() {
0788: //System.out.println("DEBUG: about to BP.doFinishLoopIteration() for player=" + this);
0789: EOM = true;
0790: loopAfterEOM = false;
0791: if (state > Player.PREFETCHED) {
0792:
0793: state = Player.PREFETCHED;
0794: if (loopCount > 1 || loopCount == -1) {
0795: loopAfterEOM = true;
0796: }
0797: }
0798: //System.out.println("DEBUG: finished BP.doFinishLoopIteration() for player=" + this);
0799: }
0800:
0801: /**
0802: * Description of the Method
0803: */
0804: synchronized void doNextLoopIteration() {
0805: //System.out.println("DEBUG: about to BP.doNextLoopIteration() for player=" + this);
0806: if (loopAfterEOM) {
0807: // If a loop count is set, we'll loop back to the beginning.
0808: if ((loopCount > 1) || (loopCount == -1)) {
0809: try {
0810: if (setMediaTime(0) == 0) {
0811: if (loopCount > 1) {
0812: loopCount--;
0813: }
0814: start();
0815: } else {
0816: loopCount = 1;
0817: }
0818: } catch (MediaException ex) {
0819: loopCount = 1;
0820: }
0821: } else if (loopCountSet > 1) {
0822: loopCount = loopCountSet;
0823: }
0824:
0825: loopAfterEOM = false;
0826: }
0827: //System.out.println("DEBUG: finished BP.doNextLoopIteration() for player=" + this);
0828: }
0829:
0830: // "final" to verify that no subclass overrides getControls.
0831: // can be removed if overload necessary
0832: /**
0833: * Gets the controls attribute of the BasicPlayer object
0834: *
0835: * @return The controls value
0836: */
0837: public final Control[] getControls() {
0838: chkClosed(true);
0839:
0840: Vector v = new Vector(3);
0841: // average maximum number of controls
0842: for (int i = 0; i < control_names.length; i++) {
0843: Object c = getControl(control_names[i]);
0844: if ((c != null) && !v.contains(c)) {
0845: v.addElement(c);
0846: }
0847: }
0848: Control[] ret = new Control[v.size()];
0849: v.copyInto(ret);
0850: return ret;
0851: }
0852:
0853: /**
0854: * Gets the <code>Control</code> that supports the specified
0855: * class or interface. The full class
0856: * or interface name should be specified.
0857: * <code>Null</code> is returned if the <code>Control</code>
0858: * is not supported.
0859: *
0860: * @param type Description of the Parameter
0861: * @return <code>Control</code> for the class or interface
0862: * name.
0863: */
0864: public final Control getControl(String type) {
0865: chkClosed(true);
0866:
0867: if (type == null) {
0868: throw new IllegalArgumentException();
0869: }
0870:
0871: // Prepend the package name if the type given does not
0872: // have the package prefix.
0873: if (type.indexOf('.') < 0) {
0874: // for non-fully qualified control names,
0875: // look up the package in the allCtrls array
0876: for (int i = 0; i < allCtrls.length; i++) {
0877: if (allCtrls[i].equals(type)) {
0878: // standard controls are specified
0879: // without package name in allCtrls
0880: return doGetControl(pkgName + type);
0881: } else if (allCtrls[i].endsWith(type)) {
0882: // non-standard controls are with
0883: // full package name in allCtrls
0884: return doGetControl(allCtrls[i]);
0885: }
0886: }
0887: }
0888: return doGetControl(type);
0889: }
0890:
0891: /**
0892: * The worker method to actually obtain the control.
0893: *
0894: * @param type the class name of the <code>Control</code>.
0895: * @return <code>Control</code> for the class or interface
0896: * name.
0897: */
0898: protected abstract Control doGetControl(String type);
0899:
0900: /**
0901: * For global PlayerID management
0902: *
0903: * @param pid Description of the Parameter
0904: * @return Description of the Return Value
0905: */
0906: public static ABBBasicPlayer get(int pid) {
0907: return (ABBBasicPlayer) (mplayers.get(new Integer(pid)));
0908: }
0909:
0910: /**
0911: * Pauses and deallocates all media players.
0912: *
0913: * After this call all players are either in realized
0914: * or unrealized state.
0915: *
0916: * Resources are being released during deallocation.
0917: */
0918: public static void pauseAll() {
0919: //System.out.println("DEBUG: about to BP.pauseAll()");
0920: if (mplayers == null) {
0921: return;
0922: }
0923:
0924: for (Enumeration e = mplayers.elements(); e.hasMoreElements();) {
0925: ABBBasicPlayer p = (ABBBasicPlayer) e.nextElement();
0926:
0927: int state = p.getState();
0928: long time = p.getMediaTime();
0929:
0930: // save the player's state
0931: pstates.put(p, new Integer(state));
0932:
0933: // save the player's media time
0934: mtimes.put(p, new Long(time));
0935:
0936: // deallocate the player
0937: //
0938: // this will implicitly stop all players
0939: // and release scarce resources such as
0940: // the audio device
0941: p.deallocate();
0942: }
0943: }
0944:
0945: /**
0946: * Resumes all media players' activities.
0947: *
0948: * Players that were in STARTED state when pause
0949: * was called will resume playing at the media time
0950: * they were stopped and deallocated.
0951: */
0952: public static void resumeAll() {
0953: //System.out.println("DEBUG: about to BP.resumeAll()");
0954: if (mplayers == null || pstates.size() == 0) {
0955: return;
0956: }
0957:
0958: for (Enumeration e = mplayers.elements(); e.hasMoreElements();) {
0959: ABBBasicPlayer p = (ABBBasicPlayer) e.nextElement();
0960:
0961: int state = ((Integer) pstates.get(p)).intValue();
0962: long time = ((Long) mtimes.get(p)).longValue();
0963:
0964: switch (state) {
0965: case Player.PREFETCHED:
0966: try {
0967: //System.out.println("DEBUG: BP.resumeAll() for PREFETCHED player=" + p);
0968: p.prefetch();
0969: p.setMediaTime(time);
0970: } catch (MediaException ex) {
0971: }
0972: break;
0973: case Player.STARTED:
0974: try {
0975: //System.out.println("DEBUG: BP.resumeAll() for STARTED player=" + p);
0976: p.realize();
0977: p.prefetch();
0978: p.setMediaTime(time);
0979: p.start();
0980: } catch (MediaException ex) {
0981: }
0982: break;
0983: }
0984: }
0985:
0986: // clear player states and media times
0987: pstates.clear();
0988: mtimes.clear();
0989: }
0990:
0991: /**
0992: * Implementation method for VolumeControl
0993: *
0994: * @param ll Description of the Parameter
0995: * @return Description of the Return Value
0996: */
0997: public int doSetLevel(int ll) {
0998: return ll;
0999: }
1000:
1001: // JAVADOC COMMENT ELIDED
1002: public String getContentType() {
1003: chkClosed(true);
1004: return "";
1005: }
1006:
1007: }
1008:
1009: /**
1010: * The thread that's responsible for delivering Player events.
1011: * This class lives for only 5 secs. If no event comes in
1012: * 5 secs, it will exit.
1013: *
1014: * @created January 13, 2005
1015: */
1016: class PlayerEventQueue extends Thread {
1017: /**
1018: * the player instance
1019: */
1020: private ABBBasicPlayer p;
1021: /**
1022: * event info array
1023: */
1024: private EventQueueEntry evt;
1025:
1026: /**
1027: * The constructor
1028: *
1029: * @param p the instance of BasicPlayer intending to post event to
1030: * this event queue.
1031: */
1032: PlayerEventQueue(ABBBasicPlayer p) {
1033: this .p = p;
1034: evt = null;
1035: //System.out.println("DEBUG: Created Player Event Queue ! player=" + p);
1036: start();
1037: }
1038:
1039: /**
1040: * Put an event in the event queue and wake up the thread to
1041: * deliver it. If the event queue is filled, block.
1042: *
1043: * @param evtName Description of the Parameter
1044: * @param evtData Description of the Parameter
1045: */
1046: synchronized void sendEvent(String evtName, Object evtData) {
1047:
1048: //System.out.println("DEBUG: about to Queue.sendEvent(" + evtName + "," + evtData +") for player=" + this);
1049:
1050: //add element to the ring ...
1051: if (evt == null) {
1052: evt = new EventQueueEntry(evtName, evtData);
1053: } else {
1054: evt.link = new EventQueueEntry(evtName, evtData, evt.link);
1055: evt = evt.link;
1056: }
1057: this .notifyAll();
1058: }
1059:
1060: /**
1061: * Event handling thread.
1062: */
1063: public void run() {
1064:
1065: String evtName = "";
1066: Object evtData = null;
1067: EventQueueEntry evtLink = null;
1068:
1069: boolean evtToGo = false; // true if there is an event to send
1070:
1071: // true if at least one event is sent,
1072: // in case that posting the initial event
1073: // takes a long time
1074: boolean evtSent = false;
1075:
1076: for (;;) {
1077:
1078: synchronized (this ) {
1079: // TBD: use a special Object to wait/notify
1080: // instead of time delays
1081: // (for synchronization and wake up of
1082: // BasicPlayer.sendEvent(...);s threads and
1083: // PlayerEventQueue.run() thread ) ?
1084: //
1085: // If the queue is empty, we'll wait for at most
1086: // 5 secs.
1087: if (evt == null) {
1088: try {
1089: this .wait(5000);
1090: } catch (InterruptedException ie) {
1091: }
1092: }
1093: if (evt != null) {
1094: evtLink = evt.link;
1095: //exclude element from the ring ...
1096: if (evtLink == evt) {
1097: evt = null;
1098: } else {
1099: evt.link = evtLink.link;
1100: }
1101: evtToGo = true;
1102:
1103: evtName = evtLink.name;
1104: evtData = evtLink.data;
1105:
1106: // For garbage collection.
1107: evtLink.link = null;
1108: evtLink.name = null;
1109: evtLink.data = null;
1110: evtLink = null;
1111:
1112: } else {
1113: evtToGo = false;
1114: }
1115:
1116: }
1117: // synchronized this
1118:
1119: if (evtToGo) {
1120: // TBD: move it to "sendEvent(...)" to provide loop-related
1121: // reaction on EOM earlier ?
1122: //
1123: // First, check and handle EOM.
1124: if (evtName == PlayerListener.END_OF_MEDIA) {
1125: p.doFinishLoopIteration();
1126: }
1127:
1128: //System.out.println("DEBUG: about to notifyListeners(" + evtName + "," + evtData +") for player=" + p);
1129: // Notify the PlayerListeners.
1130: p.notifyListeners(evtName, evtData);
1131:
1132: // We'll need to loop back if looping was set.
1133: p.doNextLoopIteration();
1134:
1135: evtSent = true;
1136:
1137: }
1138: // if (evtToGo)
1139:
1140: // We'll exit the event thread if we have already sent one
1141: // event and there's no more event after 5 secs; or if the
1142: // Player is closed.
1143:
1144: if (evtName == PlayerListener.CLOSED
1145: || evtName == PlayerListener.ERROR) {
1146: // try to nullify queue reference and exit
1147: // if player is closing ...
1148: synchronized (p.evtLock) {
1149: //System.out.println("DEBUG: Killed Player Event Queue (STOP/ERROR)! player=" + p);
1150: p.eventQueue = null;
1151: break; // Exit the event thread.
1152: }
1153: }
1154:
1155: synchronized (this ) {
1156: // try to nullify queue reference and exit
1157: // if nothing to send (but there were events in the past) ...
1158: if (evt == null && evtSent && !evtToGo) {
1159: synchronized (p.evtLock) {
1160: //System.out.println("DEBUG: Killed Player Event Queue (empty for 5 sec)! player=" + p);
1161: p.eventQueue = null;
1162: break; // Exit the event thread.
1163: }
1164: }
1165: }
1166: }
1167: }
1168: }
1169:
1170: class EventQueueEntry {
1171: String name;
1172: Object data;
1173: EventQueueEntry link;
1174:
1175: public EventQueueEntry(String n, Object d) {
1176: name = n;
1177: data = d;
1178: link = this ;
1179: }
1180:
1181: public EventQueueEntry(String n, Object d, EventQueueEntry l) {
1182: name = n;
1183: data = d;
1184: link = l;
1185: }
1186: }
|