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.io.IOException;
0027: import java.io.ByteArrayOutputStream;
0028: import java.util.Vector;
0029:
0030: import javax.microedition.media.Control;
0031: import javax.microedition.media.MediaException;
0032: import javax.microedition.media.PlayerListener;
0033: import javax.microedition.media.control.VideoControl;
0034: import javax.microedition.media.control.FramePositioningControl;
0035: import javax.microedition.media.control.RateControl;
0036: import javax.microedition.media.control.StopTimeControl;
0037:
0038: import com.sun.mmedia.Configuration;
0039: import com.sun.mmedia.VideoRenderer;
0040:
0041: /**
0042: * A player for the GIF89a.
0043: *
0044: * @created January 30, 2004
0045: */
0046: final public class GIFPlayer extends BasicPlayer implements Runnable {
0047: /* Single image decoder */
0048: private GIFImageDecoder imageDecoder;
0049:
0050: /* the width of a video frame */
0051: private int videoWidth;
0052:
0053: /* the height of a video frame */
0054: private int videoHeight;
0055:
0056: /* a full GIF frame, also called the reference frame */
0057: private int[] referenceFrame = null;
0058:
0059: /* the play thread */
0060: private Thread playThread; // default is null
0061:
0062: /* flag indicating whether to end the play thread.
0063: * done is set to true upon stopping or closing the player
0064: * or when the frame count progresses to the total number
0065: * of frames in this movie.
0066: */
0067: private boolean done;
0068:
0069: /* the start time in milliseconds.
0070: * startTime is initialized upon start of the play thread.
0071: */
0072: private long startTime;
0073:
0074: /* early threshold value for the display time in milliseconds. */
0075: private long EARLY_THRESHOLD = 100;
0076:
0077: /* minimum wait time */
0078: private final long MIN_WAIT = 50;
0079:
0080: /* For zero duration GIFs (e.g. non-animated) wait time between STARTED and END_OF_MEDIA */
0081: private final long ZERO_DURATION_WAIT = 50;
0082:
0083: /* a table of frame durations */
0084: private Vector frameTimes;
0085:
0086: /* the frame count, shows number of rendered frames, and index of next frame to render */
0087: private int frameCount;
0088:
0089: /* Last frame duration while scanning frames */
0090: private int scanFrameTime;
0091:
0092: /* elapsed media time since start of stream */
0093: private long mediaTimeOffset;
0094:
0095: /* the display time of the last read & created frame */
0096: private long displayTime; // default is 0
0097:
0098: /* the video renderer object for the GIF Player */
0099: private VideoRenderer videoRenderer;
0100:
0101: /* the video control object for the GIF Player */
0102: private VideoControl videoControl;
0103:
0104: /* the frame positioning control object for the GIF Player */
0105: private FramePosCtrl framePosControl;
0106:
0107: /* the rate control object for the GIF Player */
0108: private RateCtrl rateControl;
0109:
0110: /* the duration of the movie in microseconds */
0111: private long duration;
0112:
0113: /* the seek type of the stream: either <code>NOT_SEEKABLE</code>,
0114: * <code>SEEKABLE_TO_START</code> or <code>RANDOM_ACCESSIBLE</code>
0115: */
0116: private int seekType;
0117:
0118: /* the position in the source stream directly after the GIF header */
0119: private long firstFramePos;
0120:
0121: /* stopped flag */
0122: private boolean stopped;
0123:
0124: /* The lock object of play thread */
0125: private Object playLock = new Object();
0126:
0127: /* image data */
0128: private byte[] imageData;
0129: private int imageDataLength;
0130: private int lzwCodeSize;
0131:
0132: protected Control doGetControl(String type) {
0133: if (type.startsWith(BasicPlayer.pkgName)) {
0134:
0135: type = type.substring(BasicPlayer.pkgName.length());
0136:
0137: if (type.equals(BasicPlayer.vicName)
0138: || type.equals(BasicPlayer.guiName)) {
0139: // video control
0140: return videoControl;
0141: } else if (type.equals(BasicPlayer.fpcName)) {
0142: // frame positioning control
0143: return framePosControl;
0144: } else if (type.equals(BasicPlayer.racName)) {
0145: // rate control
0146: return rateControl;
0147: } else if (type.equals(BasicPlayer.stcName)) {
0148: // stop time control
0149:
0150: // StopTimeControl is implemented BasicPlayer,
0151: // the parent class of GIF Player
0152: return this ;
0153: }
0154: }
0155: return null;
0156: }
0157:
0158: /**
0159: * Retrieves the duration of the GIF movie.
0160: *
0161: * @return the duration in microseconds.
0162: */
0163: protected long doGetDuration() {
0164: return duration;
0165: }
0166:
0167: protected long doGetMediaTime() {
0168: long mediaTime;
0169:
0170: if (getState() < STARTED) {
0171: mediaTime = mediaTimeOffset;
0172: } else {
0173: mediaTime = ((System.currentTimeMillis() - startTime) * 1000)
0174: + mediaTimeOffset;
0175: mediaTime *= (rateControl.getRate() / 100000.0);
0176: }
0177:
0178: if (mediaTime >= duration) {
0179: return duration;
0180: }
0181:
0182: return mediaTime;
0183: }
0184:
0185: protected long doSetMediaTime(long now) throws MediaException {
0186: if (seekType == NOT_SEEKABLE)
0187: throw new MediaException("stream not seekable");
0188:
0189: if (state == STARTED)
0190: doStop();
0191:
0192: if (now > duration)
0193: now = duration;
0194:
0195: mediaTimeOffset = now;
0196:
0197: try {
0198: int count = framePosControl.mapTimeToFrame(now);
0199: //System.out.println("SetMediaTime to " + now + " (frame = " + count + "), frameCount=" + frameCount);
0200:
0201: if (count + 1 < frameCount) {
0202: // rewind to beginning
0203: frameCount = 0;
0204: seekFirstFrame();
0205: }
0206:
0207: // skip frames
0208: while (frameCount <= count && getFrame())
0209: // We need to decode all frames to have the correct pixels
0210: // for frames with transparent color
0211: decodeFrame();
0212:
0213: displayTime = getDuration(frameCount) / 1000;
0214: //System.out.println("SetMediaTime: displayTime = " + displayTime + "; frameCount=" + frameCount);
0215:
0216: renderFrame();
0217:
0218: if (state == STARTED)
0219: // restart the player
0220: doStart();
0221: } catch (IOException e) {
0222: throw new MediaException(e.getMessage());
0223: }
0224:
0225: return now;
0226: }
0227:
0228: protected void doRealize() throws MediaException {
0229: duration = TIME_UNKNOWN;
0230: frameCount = 0;
0231: mediaTimeOffset = 0;
0232:
0233: seekType = stream.getSeekType();
0234:
0235: // parse GIF header
0236: if (parseHeader()) {
0237: scanFrames();
0238:
0239: // initialize video control
0240: videoRenderer = Configuration.getConfiguration()
0241: .getVideoRenderer(this , videoWidth, videoHeight);
0242: videoControl = (VideoControl) videoRenderer
0243: .getVideoControl();
0244: videoRenderer.initRendering(VideoRenderer.XBGR888
0245: | VideoRenderer.USE_ALPHA, videoWidth, videoHeight);
0246:
0247: // initialize frame positioning control
0248: framePosControl = new FramePosCtrl();
0249:
0250: // initialize rate control
0251: rateControl = new RateCtrl();
0252:
0253: referenceFrame = null;
0254:
0255: } else
0256: throw new MediaException("invalid GIF header");
0257:
0258: }
0259:
0260: protected void doPrefetch() throws MediaException {
0261: if (referenceFrame == null)
0262: referenceFrame = new int[videoWidth * videoHeight];
0263:
0264: try {
0265: frameCount = 0;
0266: seekFirstFrame();
0267:
0268: // get first frame
0269: if (!getFrame())
0270: throw new MediaException("can't get first frame");
0271:
0272: decodeFrame();
0273:
0274: // If duration is 0 prepare the last frame once.
0275: if (duration == 0) {
0276: while (getFrame())
0277: decodeFrame();
0278: renderFrame();
0279: }
0280:
0281: } catch (IOException e) {
0282: throw new MediaException("can't seek first frame");
0283: }
0284: }
0285:
0286: protected boolean doStart() {
0287: if (duration == 0) { // e.g. for non-animated GIFs
0288: new Thread(new Runnable() {
0289: synchronized public void run() {
0290: try {
0291: wait(ZERO_DURATION_WAIT);
0292: } catch (InterruptedException ie) {
0293: }
0294: sendEvent(PlayerListener.END_OF_MEDIA, new Long(0));
0295: }
0296: }).start();
0297: } else {
0298: startTime = System.currentTimeMillis();
0299:
0300: if (stopped) {
0301: // wake up existing play thread
0302: stopped = false;
0303:
0304: synchronized (playLock) {
0305: playLock.notifyAll();
0306: }
0307: } else {
0308: displayTime = getFrameInterval(frameCount) / 1000;
0309:
0310: // Ensure that previous thread has finished
0311: playThreadFinished();
0312:
0313: synchronized (playLock) {
0314: if (playThread == null) {
0315: // Check for null is a protection against several
0316: // simultaneous doStart()'s trying to create a new thread.
0317: // But if playThreadFinished() failed to terminate
0318: // playThread, we can have a problem
0319:
0320: // create a new play thread
0321: playThread = new Thread(this );
0322: playThread.start();
0323: }
0324: }
0325: }
0326: }
0327: return true;
0328: }
0329:
0330: protected void doStop() throws MediaException {
0331: if (stopped)
0332: return;
0333:
0334: synchronized (playLock) {
0335: try {
0336: if (playThread != null) {
0337: stopped = true;
0338: playLock.notifyAll();
0339: mediaTimeOffset = doGetMediaTime();
0340: startTime = 0;
0341: playLock.wait();
0342: }
0343: } catch (InterruptedException ie) {
0344: //do nothing
0345: }
0346: }
0347: }
0348:
0349: protected void doDeallocate() {
0350: playThreadFinished();
0351:
0352: stopped = false;
0353: referenceFrame = null;
0354: }
0355:
0356: protected void doClose() {
0357: done = true;
0358:
0359: if (videoRenderer != null) {
0360: videoRenderer.close();
0361: videoRenderer = null;
0362: }
0363:
0364: frameTimes = null;
0365: imageDecoder = null;
0366: imageData = null;
0367: }
0368:
0369: public void run() {
0370: done = false;
0371:
0372: while (!done) {
0373: if (!stopped)
0374: processFrame();
0375:
0376: if (stopped) {
0377: synchronized (playLock) {
0378: playLock.notifyAll();
0379:
0380: try {
0381: playLock.wait();
0382: } catch (InterruptedException e) {
0383: // nothing to do
0384: }
0385: }
0386: }
0387: }
0388:
0389: if (!stopped && !framePosControl.isActive()) {
0390: // the run loop may have terminated prematurely, possibly
0391: // due to an I/O error...
0392: // In this case, the duration needs to be updated.
0393: if (frameCount < frameTimes.size()) {
0394: duration = getDuration(frameCount);
0395:
0396: sendEvent(PlayerListener.DURATION_UPDATED, new Long(
0397: duration));
0398: }
0399:
0400: // send an end-of-media if the player was not stopped
0401: // and the run loop terminates because the end of media
0402: // was reached.
0403: mediaTimeOffset = doGetMediaTime();
0404: startTime = 0;
0405:
0406: sendEvent(PlayerListener.END_OF_MEDIA, new Long(
0407: mediaTimeOffset));
0408: }
0409:
0410: synchronized (playLock) {
0411: playThread = null;
0412: playLock.notifyAll();
0413: }
0414: }
0415:
0416: private void stopTimeReached() {
0417: // stop the player
0418: mediaTimeOffset = doGetMediaTime();
0419: stopped = true;
0420: startTime = 0;
0421: // send STOPPED_AT_TIME event
0422: satev();
0423: }
0424:
0425: /**
0426: * Ensures that playThread dies
0427: */
0428: private void playThreadFinished() {
0429: synchronized (playLock) {
0430: // stop the playThread if it was created and started
0431: if (playThread != null) {
0432: done = true;
0433:
0434: // wake up the play thread if it was stopped
0435: playLock.notifyAll();
0436:
0437: // wait for the play thread to terminate gracefully
0438: try {
0439: // set maximum wait limit in case anything goes wrong.
0440: playLock.wait(5000);
0441: } catch (InterruptedException e) {
0442: // nothing to do.
0443: }
0444: }
0445: }
0446: }
0447:
0448: private long getDuration(int frameCount) {
0449: long duration = 0;
0450:
0451: for (int i = 0; i < frameCount; i++) {
0452: duration += ((Long) frameTimes.elementAt(i)).longValue();
0453: }
0454:
0455: return duration;
0456: }
0457:
0458: private long getFrameInterval(int frameCount) {
0459: long interval = 0;
0460:
0461: if (frameCount > 0 && frameCount <= frameTimes.size()) {
0462: interval = ((Long) frameTimes.elementAt(frameCount - 1))
0463: .longValue();
0464: }
0465:
0466: return interval;
0467: }
0468:
0469: private int timeToFrame(long mediaTime) {
0470: int frame = 0;
0471:
0472: long elapsedTime = 0;
0473:
0474: for (int i = 0; i < frameTimes.size(); i++) {
0475: long interval = ((Long) frameTimes.elementAt(i))
0476: .longValue();
0477:
0478: elapsedTime += interval;
0479:
0480: if (elapsedTime <= mediaTime)
0481: frame++;
0482: else
0483: break;
0484: }
0485:
0486: return frame;
0487: }
0488:
0489: private long frameToTime(int frameNumber) {
0490: long elapsedTime = 0;
0491:
0492: for (int i = 0; i < frameTimes.size(); i++) {
0493: long interval = ((Long) frameTimes.elementAt(i))
0494: .longValue();
0495:
0496: if (i < frameNumber)
0497: elapsedTime += interval;
0498: else
0499: break;
0500: }
0501:
0502: return elapsedTime;
0503: }
0504:
0505: private void processFrame() {
0506: // the media time in milliseconds
0507: long mediaTime = doGetMediaTime() / 1000;
0508:
0509: // frame interval in milliseconds
0510: long frameInterval = getFrameInterval(frameCount) / 1000;
0511: //System.out.println("Frame: " + frameCount + ", length: " + frameInterval + ", at: " + mediaTime + ", displayTime: " + displayTime);
0512:
0513: if (mediaTime + EARLY_THRESHOLD > displayTime) {
0514: // get the next frame
0515: if (!getFrame()) {
0516: // wait until end of last frame
0517: synchronized (playLock) {
0518: try {
0519: long waitTime = displayTime - mediaTime;
0520:
0521: if (waitTime > 0)
0522: playLock.wait(waitTime);
0523:
0524: } catch (InterruptedException e) {
0525: // nothing to do
0526: }
0527: }
0528: done = true;
0529: return;
0530: }
0531: decodeFrame();
0532:
0533: // frame interval in milliseconds
0534: frameInterval = getFrameInterval(frameCount) / 1000;
0535:
0536: // move display time to end of frame
0537: displayTime += frameInterval;
0538: }
0539:
0540: // render last read frame
0541: renderFrame();
0542:
0543: // report that stop time has been reached if
0544: // the mediaTime is greater or equal to stop time.
0545: if (stopTime != StopTimeControl.RESET
0546: && doGetMediaTime() >= stopTime) {
0547: stopTimeReached();
0548: }
0549:
0550: if (!stopped) {
0551: // threshold levels in milliseconds
0552: // It makes playback falter if frame intervals differ
0553: //EARLY_THRESHOLD = 250;
0554: //if (frameInterval > 0 && frameInterval < EARLY_THRESHOLD)
0555: // EARLY_THRESHOLD = frameInterval / 2;
0556:
0557: mediaTime = doGetMediaTime() / 1000;
0558:
0559: if (mediaTime + EARLY_THRESHOLD <= displayTime) {
0560: // wait for a bit
0561: synchronized (playLock) {
0562: try {
0563: if (!done) {
0564: mediaTime = doGetMediaTime() / 1000;
0565:
0566: long waitTime = displayTime
0567: - EARLY_THRESHOLD - mediaTime;
0568:
0569: while (!stopped && waitTime > 0) {
0570: if (waitTime > MIN_WAIT) {
0571: playLock.wait(MIN_WAIT);
0572: waitTime -= MIN_WAIT;
0573: } else {
0574: playLock.wait(waitTime);
0575: waitTime = 0;
0576: }
0577:
0578: if (stopTime != StopTimeControl.RESET
0579: && doGetMediaTime() >= stopTime) {
0580: stopTimeReached();
0581: }
0582: }
0583: }
0584: } catch (InterruptedException e) {
0585: // nothing to do
0586: }
0587: }
0588: }
0589: }
0590: }
0591:
0592: private void seekFirstFrame() throws IOException {
0593: if (seekType == RANDOM_ACCESSIBLE) {
0594: // seek to the beginning of the first frame
0595: stream.seek(firstFramePos);
0596: } else { // SEEKABLE_TO_START
0597: // seek to the start of stream and parse the header
0598: stream.seek(0);
0599: parseHeader();
0600: }
0601: imageDecoder.clearImage();
0602: }
0603:
0604: private void decodeFrame() {
0605: if (imageData != null && imageDecoder != null
0606: && referenceFrame != null)
0607: imageDecoder.decodeImage(lzwCodeSize, imageDataLength,
0608: imageData, referenceFrame);
0609: }
0610:
0611: private void renderFrame() {
0612: if (referenceFrame != null)
0613: videoRenderer.render(referenceFrame);
0614: }
0615:
0616: private void scanFrames() throws MediaException {
0617: //System.out.println("scanFrames at pos " + stream.tell());
0618: frameCount = 0;
0619: scanFrameTime = 0;
0620: duration = 0;
0621:
0622: frameTimes = new Vector();
0623:
0624: boolean eos = false;
0625:
0626: do {
0627: int id;
0628:
0629: try {
0630: id = readUnsignedByte();
0631: //System.out.println("scanFrames: id=" + id);
0632: } catch (IOException e) {
0633: id = 0x3b;
0634: }
0635:
0636: if (id == 0x21) {
0637: parseControlExtension(true);
0638: } else if (id == 0x2c) {
0639: parseImageDescriptor(true);
0640: frameCount++;
0641: frameTimes.addElement(new Long(scanFrameTime));
0642: duration += scanFrameTime;
0643: scanFrameTime = 0; // ?? reset to zero
0644: } else if (id == 0x3b) {
0645: eos = true;
0646: } else {
0647: eos = true;
0648: }
0649: } while (!eos);
0650:
0651: // reset the frame counter
0652: frameCount = 0;
0653:
0654: try {
0655: seekFirstFrame();
0656: } catch (IOException e) {
0657: throw new MediaException(e.getMessage());
0658: }
0659: }
0660:
0661: private boolean getFrame() {
0662: //System.out.println("getFrame at pos " + stream.tell());
0663:
0664: if (stream.tell() == 0)
0665: parseHeader();
0666:
0667: boolean eos = false;
0668:
0669: imageData = null;
0670:
0671: do {
0672: int id;
0673:
0674: try {
0675: id = readUnsignedByte();
0676: //System.out.println("getFrame: id=" + id);
0677: } catch (IOException e) {
0678: id = 0x3b;
0679: }
0680:
0681: if (id == 0x21) {
0682: parseControlExtension(false);
0683: } else if (id == 0x2c) {
0684: parseImageDescriptor(false);
0685: } else if (id == 0x3b) {
0686: eos = true;
0687: } else {
0688: eos = true;
0689: }
0690: } while (!eos && imageData == null);
0691:
0692: if (imageData != null) {
0693: frameCount++;
0694: return true;
0695: }
0696:
0697: return false;
0698: }
0699:
0700: private boolean parseHeader() {
0701: //System.out.println("parseHeader at pos " + stream.tell());
0702:
0703: byte[] header = new byte[6];
0704:
0705: try {
0706: stream.read(header, 0, 6);
0707: } catch (IOException e) {
0708: return false;
0709: }
0710:
0711: // check that signature spells GIF
0712: if (header[0] != 'G' || header[1] != 'I' || header[2] != 'F')
0713: return false;
0714:
0715: // check that version spells either 87a or 89a
0716: if (header[3] != '8' || header[4] != '7' && header[4] != '9'
0717: || header[5] != 'a')
0718: return false;
0719:
0720: return parseLogicalScreenDescriptor();
0721: }
0722:
0723: private boolean parseLogicalScreenDescriptor() {
0724: //System.out.println("parseLogicalScreenDescriptor at pos " + stream.tell());
0725:
0726: byte[] logicalScreenDescriptor = new byte[7];
0727: byte[] globalColorTable = null;
0728:
0729: try {
0730: stream.read(logicalScreenDescriptor, 0, 7);
0731: } catch (IOException e) {
0732: return false;
0733: }
0734:
0735: // logical screen width
0736: videoWidth = readShort(logicalScreenDescriptor, 0);
0737:
0738: // logical screen height
0739: videoHeight = readShort(logicalScreenDescriptor, 2);
0740:
0741: // flags
0742: int flags = logicalScreenDescriptor[4];
0743:
0744: // global color table flag
0745: boolean globalTable = ((flags >> 7) & 0x01) == 1;
0746:
0747: // color resolution
0748: int resolution = ((flags >> 4) & 0x07) + 1;
0749:
0750: // sort flag: not used in player
0751: //int sortFlag = (flags >> 3) & 0x01;
0752:
0753: // global color table depth
0754: int tableDepth = (flags & 0x07) + 1;
0755:
0756: // background color index
0757: int index = logicalScreenDescriptor[5] & 0xff;
0758:
0759: // pixel aspect ratio: not used inplayer
0760: //int pixelAspectRatio = logicalScreenDescriptor[6];
0761:
0762: imageDecoder = new GIFImageDecoder(videoWidth, videoHeight,
0763: resolution);
0764:
0765: if (globalTable) {
0766: int size = 3 * (1 << tableDepth);
0767: globalColorTable = new byte[size];
0768:
0769: try {
0770: stream.read(globalColorTable, 0, size);
0771: } catch (IOException e) {
0772: }
0773:
0774: imageDecoder.setGlobalPalette(tableDepth, globalColorTable,
0775: index);
0776: }
0777:
0778: firstFramePos = stream.tell();
0779:
0780: return true;
0781: }
0782:
0783: private int readShort(byte data[], int offset) {
0784: int lo = data[offset] & 0xff;
0785: int hi = data[offset + 1] & 0xff;
0786:
0787: return lo + (hi << 8);
0788: }
0789:
0790: private int readShort() {
0791: int val = 0;
0792:
0793: try {
0794: int lo = readUnsignedByte();
0795: int hi = readUnsignedByte();
0796:
0797: val = lo + (hi << 8);
0798: } catch (IOException e) {
0799: }
0800:
0801: return val;
0802: }
0803:
0804: private void parseImageDescriptor(boolean scan) {
0805: //System.out.println("parseImageDescriptor at pos " + stream.tell());
0806: byte[] imageDescriptor = new byte[9];
0807: byte[] localColorTable = null;
0808:
0809: try {
0810: stream.read(imageDescriptor, 0, 9);
0811: } catch (IOException e) {
0812: }
0813:
0814: // packed fields
0815: int flags = imageDescriptor[8];
0816:
0817: // local color table flag
0818: boolean localTable = ((flags >> 7) & 1) == 1;
0819:
0820: int tableDepth = (flags & 0x07) + 1;
0821:
0822: if (localTable) {
0823: int size = 3 * (1 << tableDepth);
0824:
0825: localColorTable = new byte[size];
0826:
0827: try {
0828: stream.read(localColorTable, 0, size);
0829: } catch (IOException e) {
0830: }
0831: }
0832:
0833: if (!scan) {
0834: // image left position
0835: int leftPos = readShort(imageDescriptor, 0);
0836:
0837: // image top position
0838: int topPos = readShort(imageDescriptor, 2);
0839:
0840: // image width
0841: int width = readShort(imageDescriptor, 4);
0842:
0843: // image height
0844: int height = readShort(imageDescriptor, 6);
0845:
0846: // interlace flag
0847: boolean interlaceFlag = ((flags >> 6) & 0x01) == 1;
0848:
0849: // sort flag: not used in player
0850: //int sortFlag = (flags >> 5) & 0x01;
0851:
0852: imageDecoder.newFrame(leftPos, topPos, width, height,
0853: interlaceFlag);
0854:
0855: // local color table size
0856: if (localTable)
0857: imageDecoder.setLocalPalette(tableDepth,
0858: localColorTable);
0859: }
0860:
0861: parseImageData();
0862: }
0863:
0864: private void parseImageData() {
0865: //System.out.println("parseImageData at pos " + stream.tell());
0866: int idx = 0;
0867:
0868: try {
0869: lzwCodeSize = readUnsignedByte();
0870:
0871: if (imageData == null)
0872: imageData = new byte[1024];
0873:
0874: int size;
0875:
0876: do {
0877: size = readUnsignedByte();
0878:
0879: if (imageData.length < idx + size) {
0880: // increase image data buffer
0881: byte data[] = new byte[idx + size];
0882: System.arraycopy(imageData, 0, data, 0, idx);
0883: imageData = data;
0884: }
0885:
0886: if (size > 0)
0887: idx += stream.read(imageData, idx, size);
0888:
0889: } while (size != 0);
0890:
0891: //imageDataLength = idx;
0892: } catch (IOException e) {
0893: //imageDataLength = 0;
0894: }
0895: // Supporting unfinished GIFs
0896: imageDataLength = idx;
0897: //System.out.println("parsed image data bytes: " + idx);
0898: }
0899:
0900: private void parsePlainTextExtension() {
0901: try {
0902: // block size
0903: int size = readUnsignedByte();
0904: if (size != 12) {
0905: // ERROR
0906: }
0907:
0908: // text grid left position
0909: int leftPos = readShort();
0910:
0911: // text grid top position
0912: int topPos = readShort();
0913:
0914: // text grid width
0915: int width = readShort();
0916:
0917: // text grid height
0918: int height = readShort();
0919:
0920: // character cell width
0921: int cellWidth = readUnsignedByte();
0922:
0923: // character cell height
0924: int cellHeight = readUnsignedByte();
0925:
0926: // text foreground color index
0927: int fgIndex = readUnsignedByte();
0928:
0929: // text background color index
0930: int bgIndex = readUnsignedByte();
0931:
0932: // plain text data
0933: do {
0934: size = readUnsignedByte();
0935:
0936: if (size > 0) {
0937: byte[] data = new byte[size];
0938:
0939: stream.read(data, 0, size);
0940: }
0941: } while (size != 0);
0942: } catch (IOException e) {
0943: }
0944: }
0945:
0946: private void parseControlExtension(boolean scan) {
0947: //System.out.println("parseControlExtension at pos " + stream.tell());
0948: try {
0949: int label = readUnsignedByte();
0950:
0951: if (label == 0xff) {
0952: parseApplicationExtension();
0953: } else if (label == 0xfe) {
0954: parseCommentExtension();
0955: } else if (label == 0xf9) {
0956: parseGraphicControlExtension(scan);
0957: } else if (label == 0x01) {
0958: parsePlainTextExtension();
0959: } else {
0960: // unkown control extension
0961: }
0962: } catch (IOException e) {
0963: }
0964: }
0965:
0966: private void parseApplicationExtension() {
0967: //System.out.println("parseApplicationExtension at pos " + stream.tell());
0968: try {
0969: // block size
0970: int size = readUnsignedByte();
0971:
0972: if (size != 11) {
0973: // System.out.println("ERROR");
0974: }
0975:
0976: // application identifier
0977: byte[] data = new byte[8];
0978: stream.read(data, 0, 8);
0979:
0980: // application authentication code
0981: data = new byte[3];
0982: stream.read(data, 0, 3);
0983:
0984: do {
0985: size = readUnsignedByte();
0986:
0987: if (size > 0) {
0988: data = new byte[size];
0989:
0990: stream.read(data, 0, size);
0991: }
0992: } while (size != 0);
0993: } catch (IOException e) {
0994: }
0995: }
0996:
0997: private void parseCommentExtension() {
0998: //System.out.println("parseCommentExtension at pos " + stream.tell());
0999: try {
1000: int size;
1001:
1002: do {
1003: size = readUnsignedByte();
1004:
1005: if (size > 0) {
1006: byte[] data = new byte[size];
1007:
1008: stream.read(data, 0, size);
1009: }
1010: } while (size != 0);
1011: } catch (IOException e) {
1012: }
1013: }
1014:
1015: private void parseGraphicControlExtension(boolean scan) {
1016: //System.out.println("parseGraphicControlExtension at pos " + stream.tell());
1017:
1018: byte[] graphicControl = new byte[6];
1019:
1020: try {
1021: stream.read(graphicControl, 0, 6);
1022: } catch (IOException e) {
1023: }
1024:
1025: // block size: not used in player - validation only
1026: //int size = graphicControl[0] & 0xff;
1027:
1028: //if (size != 4) {
1029: // ERROR: invalid block size in graphic control
1030: //}
1031:
1032: if (scan) {
1033: // delay time
1034: scanFrameTime = readShort(graphicControl, 2) * 10000;
1035: } else {
1036: // packed field
1037: int flags = graphicControl[1] & 0xff;
1038:
1039: // transparency flag
1040: boolean transparencyFlag = (flags & 0x01) == 1;
1041:
1042: // user input: not used in player
1043: //int userInput = (flags & 0x02) == 2;
1044:
1045: // undraw mode
1046: int undrawMode = (flags >> 2) & 0x07;
1047:
1048: int transparencyColorIndex = -1;
1049:
1050: if (transparencyFlag)
1051: // transparent color index
1052: transparencyColorIndex = graphicControl[4] & 0xff;
1053:
1054: imageDecoder.setGraphicsControl(undrawMode,
1055: transparencyColorIndex);
1056: }
1057: // block terminator: shoud be 0
1058: //int terminator = graphicControl[5] & 0xff;
1059: }
1060:
1061: private byte[] oneByte = new byte[1];
1062:
1063: private int readUnsignedByte() throws IOException {
1064: if (stream.read(oneByte, 0, 1) == -1)
1065: throw new IOException();
1066:
1067: return oneByte[0] & 0xff;
1068: }
1069:
1070: class FramePosCtrl implements FramePositioningControl {
1071: /**
1072: * indicates whether the frame positioning control
1073: * is actively engaged.
1074: */
1075: private boolean active;
1076:
1077: /**
1078: * The constructor of FramePosCtrl.
1079: */
1080: FramePosCtrl() {
1081: active = false;
1082: }
1083:
1084: public int seek(int frameNumber) {
1085: active = true;
1086:
1087: // clear the End-of-media flag to ensure that
1088: // a consecutive start call will start the player
1089: // from the seek position and not from the first
1090: // frame.
1091: EOM = false;
1092:
1093: if (frameNumber < 0) {
1094: frameNumber = 0;
1095: } else if (frameNumber >= frameTimes.size()) {
1096: frameNumber = frameTimes.size() - 1;
1097: }
1098:
1099: long time = mapFrameToTime(frameNumber);
1100:
1101: try {
1102: doSetMediaTime(time);
1103: } catch (MediaException e) {
1104: // nothing to do
1105: }
1106:
1107: active = false;
1108:
1109: return frameNumber;
1110: }
1111:
1112: public int skip(int framesToSkip) {
1113: active = true;
1114:
1115: // clear the End-of-media flag to ensure that
1116: // a consecutive start call will start the player
1117: // from the seek position and not from the first
1118: // frame.
1119: EOM = false;
1120:
1121: int frames_skipped = 0;
1122:
1123: int oldFrame = frameCount - 1;
1124:
1125: if (oldFrame < 0) {
1126: oldFrame = 0;
1127: } else if (oldFrame >= frameTimes.size()) {
1128: oldFrame = frameTimes.size() - 1;
1129: }
1130:
1131: long newFrame = (long) oldFrame + framesToSkip;
1132:
1133: if (newFrame < 0) {
1134: newFrame = 0;
1135: } else if (newFrame >= frameTimes.size()) {
1136: newFrame = frameTimes.size() - 1;
1137: }
1138:
1139: long time = mapFrameToTime((int) newFrame);
1140:
1141: try {
1142: doSetMediaTime(time);
1143:
1144: frames_skipped = (int) (newFrame - oldFrame);
1145: } catch (MediaException e) {
1146: // nothing to do
1147: }
1148:
1149: active = false;
1150:
1151: return frames_skipped;
1152: }
1153:
1154: public long mapFrameToTime(int frameNumber) {
1155: if (frameNumber < 0 || frameNumber >= frameTimes.size()) {
1156: return -1;
1157: }
1158:
1159: return (long) (frameToTime(frameNumber)
1160: * rateControl.getRate() / 100000L);
1161: }
1162:
1163: public int mapTimeToFrame(long mediaTime) {
1164: if (mediaTime < 0 || mediaTime > duration) {
1165: return -1;
1166: }
1167:
1168: long time = mediaTime * rateControl.getRate() / 100000;
1169:
1170: return (int) timeToFrame(time);
1171: }
1172:
1173: public boolean isActive() {
1174: return active;
1175: }
1176: }
1177:
1178: class RateCtrl implements RateControl {
1179: /* the playback rate in 1000 times the percentage of the
1180: * actual rate.
1181: */
1182: private int rate;
1183:
1184: /* the minimum playback rate */
1185: private final int MIN_PLAYBACK_RATE = 10000; // 10%
1186:
1187: /* the maximum playback rate */
1188: private final int MAX_PLAYBACK_RATE = 200000; // 200%
1189:
1190: RateCtrl() {
1191: rate = 100000; // normal speed, 100%
1192: }
1193:
1194: public int setRate(int millirate) {
1195: if (millirate < MIN_PLAYBACK_RATE) {
1196: rate = MIN_PLAYBACK_RATE;
1197: } else if (millirate > MAX_PLAYBACK_RATE) {
1198: rate = MAX_PLAYBACK_RATE;
1199: } else {
1200: rate = millirate;
1201: }
1202:
1203: return rate;
1204: }
1205:
1206: public int getRate() {
1207: return rate;
1208: }
1209:
1210: public int getMaxRate() {
1211: return MAX_PLAYBACK_RATE;
1212: }
1213:
1214: public int getMinRate() {
1215: return MIN_PLAYBACK_RATE;
1216: }
1217: }
1218:
1219: }
|