FileDocCategorySizeDatePackage
GIFPlayer.javaAPI DocphoneME MR2 API (J2ME)35572Wed May 02 16:47:10 BST 2007com.sun.mmedia

GIFPlayer

public final class GIFPlayer extends BasicPlayer implements Runnable
A player for the GIF89a.
created
January 30, 2004

Fields Summary
private GIFImageDecoder
imageDecoder
private int
videoWidth
private int
videoHeight
private int[]
referenceFrame
private Thread
playThread
private boolean
done
private long
startTime
private long
EARLY_THRESHOLD
private final long
MIN_WAIT
private final long
ZERO_DURATION_WAIT
private Vector
frameTimes
private int
frameCount
private int
scanFrameTime
private long
mediaTimeOffset
private long
displayTime
private com.sun.mmedia.VideoRenderer
videoRenderer
private javax.microedition.media.control.VideoControl
videoControl
private FramePosCtrl
framePosControl
private RateCtrl
rateControl
private long
duration
private int
seekType
private long
firstFramePos
private boolean
stopped
private Object
playLock
private byte[]
imageData
private int
imageDataLength
private int
lzwCodeSize
private byte[]
oneByte
Constructors Summary
Methods Summary
private voiddecodeFrame()

        if (imageData != null && imageDecoder != null && referenceFrame != null)
            imageDecoder.decodeImage(lzwCodeSize, imageDataLength, imageData, referenceFrame);
    
protected voiddoClose()

        done = true;

        if (videoRenderer != null) {
            videoRenderer.close();
            videoRenderer = null;
        }

        frameTimes = null;
        imageDecoder = null;
        imageData = null;
    
protected voiddoDeallocate()

        playThreadFinished();
        
        stopped = false;
        referenceFrame = null;
    
protected javax.microedition.media.ControldoGetControl(java.lang.String type)


        
        if (type.startsWith(BasicPlayer.pkgName)) {
            
            type = type.substring(BasicPlayer.pkgName.length());
            
            if (type.equals(BasicPlayer.vicName) || 
                type.equals(BasicPlayer.guiName)) {
                // video control
                return videoControl;
            } else if (type.equals(BasicPlayer.fpcName)) { 
                // frame positioning control
                return framePosControl;
            } else if (type.equals(BasicPlayer.racName)) { 
                // rate control
                return rateControl;
            } else if (type.equals(BasicPlayer.stcName)) {
                // stop time control

                // StopTimeControl is implemented BasicPlayer,
                // the parent class of GIF Player
                return this;
            }
        }
        return null;
    
protected longdoGetDuration()
Retrieves the duration of the GIF movie.

return
the duration in microseconds.

        return duration;
    
protected longdoGetMediaTime()

        long mediaTime;

        if (getState() < STARTED) {
            mediaTime = mediaTimeOffset;
        } else {
            mediaTime = ((System.currentTimeMillis() - startTime) * 1000) + mediaTimeOffset;
            mediaTime *= (rateControl.getRate() / 100000.0);
        }

        if (mediaTime >= duration) {
            return duration;
        }

        return mediaTime;       
    
protected voiddoPrefetch()

        if (referenceFrame == null)
            referenceFrame = new int[videoWidth * videoHeight];

        try { 
            frameCount = 0;
            seekFirstFrame();         

            // get first frame
            if (!getFrame())
                throw new MediaException("can't get first frame");

            decodeFrame();

            // If duration is 0 prepare the last frame once.
            if (duration == 0) {
                while (getFrame())
                    decodeFrame();
                renderFrame();
            }

        } catch (IOException e) {
            throw new MediaException("can't seek first frame");
        }
    
protected voiddoRealize()

          
        duration = TIME_UNKNOWN;
        frameCount = 0;
        mediaTimeOffset = 0;

        seekType = stream.getSeekType();

        // parse GIF header
        if (parseHeader()) {
            scanFrames();

            // initialize video control
            videoRenderer = Configuration.getConfiguration().getVideoRenderer(
                                              this, videoWidth, videoHeight);
            videoControl = (VideoControl)videoRenderer.getVideoControl();
            videoRenderer.initRendering(VideoRenderer.XBGR888 | 
                                        VideoRenderer.USE_ALPHA,
                                        videoWidth, videoHeight);

            // initialize frame positioning control
            framePosControl = new FramePosCtrl();

            // initialize rate control
            rateControl = new RateCtrl();

            referenceFrame = null;

        } else
            throw new MediaException("invalid GIF header");

    
protected longdoSetMediaTime(long now)

        if (seekType == NOT_SEEKABLE)
            throw new MediaException("stream not seekable");

        if (state == STARTED)
            doStop();

        if (now > duration)
            now = duration;

        mediaTimeOffset = now;

        try {
            int count = framePosControl.mapTimeToFrame(now);
            //System.out.println("SetMediaTime to " + now + " (frame = " + count + "), frameCount=" + frameCount);

            if (count + 1 < frameCount) {
                // rewind to beginning
                frameCount = 0;
                seekFirstFrame();         
            }

            // skip frames
            while (frameCount <= count && getFrame())
                // We need to decode all frames to have the correct pixels
                // for frames with transparent color
                decodeFrame();

            displayTime = getDuration(frameCount) / 1000;
            //System.out.println("SetMediaTime: displayTime = " + displayTime + "; frameCount=" + frameCount);

            renderFrame();

            if (state == STARTED)
                // restart the player
                doStart();
        } catch (IOException e) {
            throw new MediaException(e.getMessage());
        }

        return now;
    
protected booleandoStart()

        if (duration == 0) { // e.g. for non-animated GIFs
            new Thread(new Runnable() {
                synchronized public void run() {
                    try {
                        wait(ZERO_DURATION_WAIT);
                    } catch (InterruptedException ie) { }
                    sendEvent(PlayerListener.END_OF_MEDIA, new Long(0));
                }
            }).start();
        } else {
            startTime = System.currentTimeMillis(); 

            if (stopped) {
                // wake up existing play thread
                stopped = false;
            
                synchronized (playLock) {
                    playLock.notifyAll();
                }
            } else {
                displayTime = getFrameInterval(frameCount) / 1000;
                
                // Ensure that previous thread has finished
                playThreadFinished();

                synchronized (playLock) {
                    if (playThread == null) {
                        // Check for null is a protection against several
                        // simultaneous doStart()'s trying to create a new thread.
                        // But if playThreadFinished() failed to terminate
                        // playThread, we can have a problem

                        // create a new play thread
                        playThread = new Thread(this);
                        playThread.start();
                    }
                }
            }
        }
        return true;
    
protected voiddoStop()

        if (stopped) return;    

        synchronized (playLock) {
            try {
                if (playThread != null) {
                    stopped = true;
                    playLock.notifyAll();               
                    mediaTimeOffset = doGetMediaTime();
                    startTime = 0;
                    playLock.wait();
                }
            } catch (InterruptedException ie) {
                //do nothing
            }
        }
    
private longframeToTime(int frameNumber)

        long elapsedTime = 0;

        for (int i = 0; i < frameTimes.size(); i++) {
            long interval = ((Long)frameTimes.elementAt(i)).longValue();
            
            if (i < frameNumber)
                elapsedTime += interval;
            else
                break;
        }

        return elapsedTime;
    
private longgetDuration(int frameCount)

        long duration = 0;
         
        for (int i = 0; i < frameCount; i++) {
            duration += ((Long)frameTimes.elementAt(i)).longValue();
        }
                    
        return duration;    
    
private booleangetFrame()

            
        //System.out.println("getFrame at pos " + stream.tell());

        if (stream.tell() == 0)
            parseHeader();

        boolean eos = false;
        
        imageData = null;
        
        do {
            int id;

            try {
                id = readUnsignedByte();
                //System.out.println("getFrame: id=" + id);
            } catch (IOException e) {
                id = 0x3b;
            }
            
            if (id == 0x21) {
                parseControlExtension(false);
            } else if (id == 0x2c) {
                parseImageDescriptor(false);
            } else if (id == 0x3b) {
                eos = true;
            } else {
                eos = true;
            }
        } while (!eos && imageData == null);    

        if (imageData != null) {
            frameCount++;
            return true;
        }

        return false;
    
private longgetFrameInterval(int frameCount)

        long interval = 0;
         
        if (frameCount > 0 && frameCount <= frameTimes.size()) {
            interval = ((Long)frameTimes.elementAt(frameCount - 1)).longValue();
        }

        return interval;    
    
private voidparseApplicationExtension()

        //System.out.println("parseApplicationExtension at pos " + stream.tell());
        try {
            // block size
            int size = readUnsignedByte();

            if (size != 11) {
                // System.out.println("ERROR");
            }

            // application identifier
            byte[] data = new byte[8];
            stream.read(data, 0, 8);

            // application authentication code
            data = new byte[3];
            stream.read(data, 0, 3);

            do {
                size = readUnsignedByte();

                if (size > 0) {
                    data = new byte[size];

                    stream.read(data, 0, size);
                }
            } while (size != 0);
        } catch (IOException e) {
        }
    
private voidparseCommentExtension()

        //System.out.println("parseCommentExtension at pos " + stream.tell());
        try {
            int size;

            do {
                size = readUnsignedByte();

                if (size > 0) {
                    byte[] data = new byte[size];

                    stream.read(data, 0, size);
                }
            } while (size != 0);
        } catch (IOException e) {
        }
    
private voidparseControlExtension(boolean scan)

        //System.out.println("parseControlExtension at pos " + stream.tell());
        try {
            int label = readUnsignedByte();

            if (label == 0xff) {
                parseApplicationExtension();
            } else if (label == 0xfe) {
                parseCommentExtension();
            } else if (label == 0xf9) {
                parseGraphicControlExtension(scan);
            } else if (label == 0x01) {
                parsePlainTextExtension();
            } else {
                // unkown control extension
            }
        } catch (IOException e) {
        }
    
private voidparseGraphicControlExtension(boolean scan)

        //System.out.println("parseGraphicControlExtension at pos " + stream.tell());
        
        byte [] graphicControl = new byte[6];

        try {
            stream.read(graphicControl, 0, 6);
        } catch (IOException e) {
        }

        // block size: not used in player - validation only
        //int size = graphicControl[0] & 0xff;

        //if (size != 4) {
            // ERROR: invalid block size in graphic control
        //}

        if (scan) {
            // delay time
            scanFrameTime = readShort(graphicControl, 2) * 10000;
        } else {
            // packed field
            int flags = graphicControl[1] & 0xff;

            // transparency flag
            boolean transparencyFlag = (flags & 0x01) == 1;

            // user input: not used in player
            //int userInput = (flags & 0x02) == 2;

            // undraw mode
            int undrawMode = (flags >> 2) & 0x07;

            int transparencyColorIndex = -1;

            if (transparencyFlag)
                // transparent color index
                transparencyColorIndex = graphicControl[4] & 0xff;

            imageDecoder.setGraphicsControl(undrawMode, transparencyColorIndex);
        }
        // block terminator: shoud be 0
        //int terminator = graphicControl[5] & 0xff;
    
private booleanparseHeader()

        //System.out.println("parseHeader at pos " + stream.tell());

        byte [] header = new byte[6];            

        try {
            stream.read(header, 0, 6);
        } catch (IOException e) {
            return false;
        }

        // check that signature spells GIF
        if (header[0] != 'G" || header[1] != 'I" || header[2] != 'F")
            return false;

        // check that version spells either 87a or 89a
        if (header[3] != '8" || header[4] != '7" && header[4] != '9" || 
            header[5] != 'a")
            return false;

        return parseLogicalScreenDescriptor();
    
private voidparseImageData()

        //System.out.println("parseImageData at pos " + stream.tell());
        int idx = 0;

        try {
            lzwCodeSize = readUnsignedByte();

            if (imageData == null)
                imageData = new byte[1024];
         
            int size;
            
            do {
                size = readUnsignedByte();
                
                if (imageData.length < idx + size) {
                    // increase image data buffer
                    byte data[] = new byte[idx + size];
                    System.arraycopy(imageData, 0, data, 0, idx);
                    imageData = data;
                }
                
                if (size > 0)
                    idx += stream.read(imageData, idx, size);
            
            } while (size != 0);
                                    
            //imageDataLength = idx;
        } catch (IOException e) {
            //imageDataLength = 0;
        }
        // Supporting unfinished GIFs
        imageDataLength = idx;
        //System.out.println("parsed image data bytes: " + idx);
    
private voidparseImageDescriptor(boolean scan)

        //System.out.println("parseImageDescriptor at pos " + stream.tell());
        byte [] imageDescriptor = new byte[9];
        byte [] localColorTable = null;

        try {
            stream.read(imageDescriptor, 0, 9);
        } catch (IOException e) {
        }

        // packed fields
        int flags = imageDescriptor[8];

        // local color table flag
        boolean localTable = ((flags >> 7) & 1) == 1;

        int tableDepth = (flags & 0x07) + 1;

        if (localTable) {
            int size = 3 * (1 << tableDepth);

            localColorTable = new byte[size];

            try {
                stream.read(localColorTable, 0, size);
            } catch (IOException e) {
            }
        }

        if (!scan) {
            // image left position
            int leftPos = readShort(imageDescriptor, 0);

            // image top position
            int topPos = readShort(imageDescriptor, 2);

            // image width
            int width = readShort(imageDescriptor, 4);

            // image height
            int height = readShort(imageDescriptor, 6);

            // interlace flag
            boolean interlaceFlag = ((flags >> 6) & 0x01) == 1;

            // sort flag: not used in player
            //int sortFlag = (flags >> 5) & 0x01;

            imageDecoder.newFrame(leftPos, topPos, width, height, interlaceFlag);

            // local color table size
            if (localTable)
                imageDecoder.setLocalPalette(tableDepth, localColorTable);
        }

        parseImageData();
    
private booleanparseLogicalScreenDescriptor()

        //System.out.println("parseLogicalScreenDescriptor at pos " + stream.tell());

        byte [] logicalScreenDescriptor = new byte[7];
        byte [] globalColorTable = null;

        try {
            stream.read(logicalScreenDescriptor, 0, 7);
        } catch (IOException e) {
            return false;
        }
            
        // logical screen width
        videoWidth = readShort(logicalScreenDescriptor, 0);

        // logical screen height
        videoHeight = readShort(logicalScreenDescriptor, 2);

        // flags
        int flags = logicalScreenDescriptor[4];

        // global color table flag
        boolean globalTable = ((flags >> 7) & 0x01) == 1;

        // color resolution
        int resolution = ((flags >> 4) & 0x07) + 1;

        // sort flag: not used in player
        //int sortFlag = (flags >> 3) & 0x01;

        // global color table depth
        int tableDepth = (flags & 0x07) + 1;

        // background color index
        int index = logicalScreenDescriptor[5] & 0xff;

        // pixel aspect ratio: not used inplayer
        //int pixelAspectRatio = logicalScreenDescriptor[6];

        imageDecoder = new GIFImageDecoder(videoWidth, videoHeight, resolution);

        if (globalTable) {
            int size = 3 * (1 << tableDepth);
            globalColorTable = new byte[size];

            try {
                stream.read(globalColorTable, 0, size);
            } catch (IOException e) {
            }

            imageDecoder.setGlobalPalette(tableDepth, globalColorTable, index);
        }
    
        firstFramePos = stream.tell();

        return true;
    
private voidparsePlainTextExtension()

        try {
            // block size
            int size = readUnsignedByte();
            if (size != 12) {
                // ERROR
            }

            // text grid left position
            int leftPos = readShort();

            // text grid top position
            int topPos = readShort();

            // text grid width
            int width = readShort();

            // text grid height
            int height = readShort();

            // character cell width
            int cellWidth = readUnsignedByte();

            // character cell height
            int cellHeight = readUnsignedByte();

            // text foreground color index
            int fgIndex = readUnsignedByte();

            // text background color index
            int bgIndex = readUnsignedByte();

            // plain text data
            do {
                size = readUnsignedByte();

                if (size > 0) {
                    byte[] data = new byte[size];

                    stream.read(data, 0, size);
                }
            } while (size != 0);
        } catch (IOException e) {
        }
    
private voidplayThreadFinished()
Ensures that playThread dies

        synchronized (playLock) {
            // stop the playThread if it was created and started
            if (playThread != null) {
                done = true;
                
                // wake up the play thread if it was stopped
                playLock.notifyAll();
                
                // wait for the play thread to terminate gracefully
                try {
                    // set maximum wait limit in case anything goes wrong.
                    playLock.wait(5000);
                } catch (InterruptedException e) {
                    // nothing to do.
                }
            }
        }
    
private voidprocessFrame()

        // the media time in milliseconds
        long mediaTime = doGetMediaTime() / 1000;

        // frame interval in milliseconds
        long frameInterval = getFrameInterval(frameCount) / 1000;
        //System.out.println("Frame: " + frameCount + ", length: " + frameInterval + ", at: " + mediaTime + ", displayTime: " + displayTime);

        if (mediaTime + EARLY_THRESHOLD > displayTime) {
            // get the next frame
            if (!getFrame()) {
                // wait until end of last frame
                synchronized (playLock) {
                    try {
                        long waitTime = displayTime - mediaTime;

                        if (waitTime > 0)
                            playLock.wait(waitTime);
                                    
                    } catch (InterruptedException e) {
                        // nothing to do
                    }               
                }
                done = true;
                return;
            }
            decodeFrame();

            // frame interval in milliseconds
            frameInterval = getFrameInterval(frameCount) / 1000;

            // move display time to end of frame
            displayTime += frameInterval;
        }

        // render last read frame
        renderFrame();

        // report that stop time has been reached if
        // the mediaTime is greater or equal to stop time.      
        if (stopTime != StopTimeControl.RESET && 
            doGetMediaTime() >= stopTime) {
            stopTimeReached();
        }
       
        if (!stopped) {
            // threshold levels in milliseconds
            // It makes playback falter if frame intervals differ
            //EARLY_THRESHOLD = 250;
            //if (frameInterval > 0 && frameInterval < EARLY_THRESHOLD)
            //    EARLY_THRESHOLD = frameInterval / 2;
                        
            mediaTime = doGetMediaTime() / 1000;

            if (mediaTime + EARLY_THRESHOLD <= displayTime) {
                // wait for a bit
                synchronized (playLock) {
                    try {
                        if (!done) {
                            mediaTime = doGetMediaTime() / 1000;

                            long waitTime = displayTime - EARLY_THRESHOLD - mediaTime;
                                
                            while (!stopped && waitTime > 0) {
                                if (waitTime > MIN_WAIT) {
                                    playLock.wait(MIN_WAIT);
                                    waitTime -= MIN_WAIT;
                                } else {
                                    playLock.wait(waitTime);
                                    waitTime = 0;
                                }
                                    
                                if (stopTime != StopTimeControl.RESET && 
                                    doGetMediaTime() >= stopTime) {
                                    stopTimeReached();
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        // nothing to do
                    }           
                }
            }
        }
    
private intreadShort(byte[] data, int offset)

        int lo = data[offset] & 0xff;
        int hi = data[offset + 1] & 0xff;
        
        return lo + (hi << 8);
    
private intreadShort()

        int val = 0;

        try {
            int lo = readUnsignedByte();
            int hi = readUnsignedByte();

            val = lo + (hi << 8);
        } catch (IOException e) {
        }

        return val;
    
private intreadUnsignedByte()


         
        if (stream.read(oneByte, 0, 1) == -1)
            throw new IOException();

        return oneByte[0] & 0xff;
    
private voidrenderFrame()

        if (referenceFrame != null)
            videoRenderer.render(referenceFrame);
    
public voidrun()

        done = false;
        
        while (!done) {
            if (!stopped)
                processFrame();

            if (stopped) {
                synchronized (playLock) {
                    playLock.notifyAll();
                
                    try {
                        playLock.wait();
                    } catch (InterruptedException e) {
                        // nothing to do
                    }
                }
            }
        }

        if (!stopped && !framePosControl.isActive()) {
            // the run loop may have terminated prematurely, possibly
            // due to an I/O error...
            // In this case, the duration needs to be updated.
            if (frameCount < frameTimes.size()) {
                duration = getDuration(frameCount);

                sendEvent(PlayerListener.DURATION_UPDATED, new Long(duration));
            }

            // send an end-of-media if the player was not stopped
            // and the run loop terminates because the end of media
            // was reached.
            mediaTimeOffset = doGetMediaTime(); 
            startTime = 0;

            sendEvent(PlayerListener.END_OF_MEDIA, new Long(mediaTimeOffset));
        }

        synchronized (playLock) {
            playThread = null;
            playLock.notifyAll();
        }
    
private voidscanFrames()

       
        //System.out.println("scanFrames at pos " + stream.tell());
        frameCount = 0;
        scanFrameTime = 0;
        duration = 0;

        frameTimes = new Vector();

        boolean eos = false;
        
        do {
            int id;

            try {
                id = readUnsignedByte();
                //System.out.println("scanFrames: id=" + id);
            } catch (IOException e) {
                id = 0x3b;
            }

            if (id == 0x21) {
                parseControlExtension(true);
            } else if (id == 0x2c) {
                parseImageDescriptor(true);
                frameCount++;
                frameTimes.addElement(new Long(scanFrameTime));
                duration += scanFrameTime;
                scanFrameTime = 0; // ?? reset to zero
            } else if (id == 0x3b) {
                eos = true;
            } else {
                eos = true;
            }
        } while (!eos); 

        // reset the frame counter
        frameCount = 0;

        try {
            seekFirstFrame();
        } catch (IOException e) {
            throw new MediaException(e.getMessage());
        }
    
private voidseekFirstFrame()

        if (seekType == RANDOM_ACCESSIBLE) {
            // seek to the beginning of the first frame
            stream.seek(firstFramePos);
        } else { // SEEKABLE_TO_START           
            // seek to the start of stream and parse the header
            stream.seek(0);
            parseHeader();
        }
        imageDecoder.clearImage();
    
private voidstopTimeReached()

        // stop the player
        mediaTimeOffset = doGetMediaTime();
        stopped = true;
        startTime = 0;        
        // send STOPPED_AT_TIME event
        satev();
    
private inttimeToFrame(long mediaTime)

        int frame = 0;

        long elapsedTime = 0;

        for (int i = 0; i < frameTimes.size(); i++) {
            long interval = ((Long)frameTimes.elementAt(i)).longValue();

            elapsedTime += interval;

            if (elapsedTime <= mediaTime)
                frame++;
            else
                break;
        }
        
        return frame;