org.llama.jmf.JMFVideoImage.java Source code

Java tutorial

Introduction

Here is the source code for org.llama.jmf.JMFVideoImage.java

Source

/******************************************************************************
 * JMF/FOBS/jME renderer. Copyright (c) 2006 Tijl Houtbeckers Coded by Tijl
 * Houtbeckers This file is part of the JMF/FOBS/jME renderer. The JMF/FOBS/jME
 * renderer is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version. The JMF/FOBS/jME renderer is distributed in the hope that
 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details. You should have received a
 * copy of the GNU Lesser General Public License along with FOBS; if not, write
 * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307 USA
 ******************************************************************************/

/**
 * @author Tijl Houtbeckers Initial release: 23-02-2006 jME: jmonkeyengine.com
 *         FOBS: http://fobs.sourceforge.net/ JMF:
 *         http://java.sun.com/products/java-media/jmf/
 */

package org.llama.jmf;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.Logger;

import javax.media.CannotRealizeException;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.EndOfMediaEvent;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoPlayerException;
import javax.media.Player;
import javax.media.ResourceUnavailableEvent;
import javax.media.Time;
import javax.media.control.FramePositioningControl;
import javax.media.format.RGBFormat;
import javax.media.protocol.DataSource;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.OpenGLException;
import org.lwjgl.opengl.Util;

import com.jme.image.Image;
import com.jme.image.Texture;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;

/**
 * The Class JMFVideoImage.
 */
public class JMFVideoImage extends Image implements ByteBufferRendererListener, ControllerListener {

    /** The Constant log. */
    private static final Logger log = Logger.getLogger(JMFVideoImage.class.getName());

    /** The Constant SCALE_FIT. */
    public final static int SCALE_FIT = 2;

    /** The Constant SCALE_MAXIMIZE. */
    public final static int SCALE_MAXIMIZE = 1;

    /** The Constant SCALE_NONE. */
    public final static int SCALE_NONE = 0;

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = -8413968528763966076L;

    /** The active. */
    private boolean active;

    /** The buffer. */
    private ByteBuffer buffer;

    /** The inittexture. */
    private transient boolean failed = false, ready = false, inittexture = false;

    /** The flipped. */
    private boolean flipped = false;

    /** The fpc. */
    private FramePositioningControl fpc;

    /** The framecounter. */
    long framecounter = 0;

    /** The jmfplayer. */
    private Player jmfplayer;

    /** The lastupdated. */
    long lastupdated = 0;

    /** The dataformat. */
    private int pixelformat, dataformat;

    /** The scalemethod. */
    private int scalemethod;

    /** The videoheight. */
    private int videowidth, videoheight; // frame dimensions

    /**
     * Instantiates a new JMF video image.
     *
     * @param filename
     *            the filename
     * @param loop
     *            the loop
     * @param scalemethod
     *            the scalemethod
     * @throws NoPlayerException
     *             the no player exception
     * @throws CannotRealizeException
     *             the cannot realize exception
     * @throws MalformedURLException
     *             the malformed url exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public JMFVideoImage(String filename, boolean loop, int scalemethod)
            throws NoPlayerException, CannotRealizeException, MalformedURLException, IOException {
        this(new URL("file:" + filename), loop, scalemethod);
    }

    /**
     * Instantiates a new JMF video image.
     *
     * @param url
     *            the url
     * @param loop
     *            the loop
     * @param scalemethod
     *            the scalemethod
     * @throws NoPlayerException
     *             the no player exception
     * @throws CannotRealizeException
     *             the cannot realize exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public JMFVideoImage(URL url, boolean loop, int scalemethod)
            throws NoPlayerException, CannotRealizeException, IOException {

        this.scalemethod = scalemethod;

        Manager.setHint(Manager.PLUGIN_PLAYER, new Boolean(true));
        ByteBufferRenderer.listener = this;

        try {

            String address = url.toString();

            address = address.replaceAll("file:/", "file:");
            address = address.replaceAll("%20", " ");

            MediaLocator locator = new MediaLocator(address);

            DataSource ds = Manager.createDataSource(locator);

            jmfplayer = Manager.createRealizedPlayer(ds);
            log.info("Created player for: " + url.toString());

            jmfplayer.addControllerListener(this);

            fpc = (FramePositioningControl) jmfplayer.getControl("javax.media.control.FramePositioningControl");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * javax.media.ControllerListener#controllerUpdate(javax.media.ControllerEvent
     * )
     */
    public void controllerUpdate(ControllerEvent evt)
    // respond to events
    {
        if (evt instanceof ResourceUnavailableEvent) {
            failed = false;
            synchronized (this) {
                this.notifyAll();
            }
        } else if (evt instanceof EndOfMediaEvent) { // make the movie loop
            log.info("loop the movie");
            // jmfplayer.setMediaTime(new Time(0));
            // jmfplayer.start();
            restart();
        }
    }

    /*
     * (non-Javadoc)
     * @see org.llama.jmf.ByteBufferRendererListener#frame(java.nio.ByteBuffer)
     */
    public void frame(ByteBuffer buffer) {
        try {
            synchronized (this) {
                this.buffer = buffer;
                framecounter++;
                // guard against wrapping around
                if (framecounter < 0) {
                    framecounter = 1;
                    lastupdated = 0;
                }
                this.notifyAll();
                while (!failed && (lastupdated < framecounter)) {
                    this.wait();
                }

            }

        } catch (Exception ignored) {
        }
    }

    /**
     * Gets the duration.
     *
     * @return the duration
     */
    public Time getDuration() {
        return jmfplayer.getDuration();
    }

    /**
     * Gets the media time.
     *
     * @return the media time
     */
    public Time getMediaTime() {
        return jmfplayer.getMediaTime();
    }

    /**
     * Gets the sound volume level.
     *
     * @return the sound volume level
     */
    public float getSoundVolumeLevel() {
        if (jmfplayer.getGainControl() != null) {
            return jmfplayer.getGainControl().getLevel();
        }
        return 0;
    }

    /**
     * Gets the speed.
     *
     * @param rate
     *            the rate
     * @return the speed
     */
    public void getSpeed(float rate) {
        jmfplayer.getRate();
    }

    /**
     * Gets the video height.
     *
     * @return the video height
     */
    public int getVideoHeight() {
        return videoheight;
    }

    /**
     * Gets the video width.
     *
     * @return the video width
     */
    public int getVideoWidth() {
        return videowidth;
    }

    /**
     * Checks if is flipped.
     *
     * @return true, if is flipped
     */
    public boolean isFlipped() {
        return flipped;
    }

    /**
     * Checks if is mute.
     *
     * @return true, if is mute
     */
    public boolean isMute() {
        if (jmfplayer.getGainControl() != null) {
            return jmfplayer.getGainControl().getMute();
        }
        return false;
    }

    /**
     * Restart.
     */
    public void restart() {
        jmfplayer.setMediaTime(new Time(0));
        jmfplayer.start();
    }

    /**
     * Seek.
     *
     * @param time
     *            the time
     */
    public void seek(Time time) {
        if (fpc != null) {
            fpc.seek(fpc.mapTimeToFrame(time));
        }
    }

    /**
     * Sets the mute.
     *
     * @param isMute
     *            the new mute
     */
    public void setMute(boolean isMute) {
        if (jmfplayer.getGainControl() != null) {
            jmfplayer.getGainControl().setMute(isMute);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.llama.jmf.ByteBufferRendererListener#setSize(int, int,
     * javax.media.format.RGBFormat)
     */
    public void setSize(int videowidth, int videoheight, RGBFormat format) {
        log.info("set Size: " + videowidth + " " + videoheight);
        if (ready) {
            return;
        }

        // TODO: support more formats, test on linux/mac (might need ARGB?)

        switch (format.getBitsPerPixel()) {
        case 32:
            pixelformat = GL12.GL_BGRA;
            this.setFormat(Format.RGBA8);
            break;
        case 16:
            pixelformat = GL12.GL_BGRA;
            this.setFormat(Format.RGB5A1);
            break;
        case 24:
        default:
            pixelformat = GL12.GL_BGR;
            this.setFormat(Format.RGB8);
        }

        // TODO: not actually used yet.
        switch (format.getPixelStride()) {
        case 1:
            if (this.getFormat() == Format.RGB5A1) {
                /*
                 * setFormat will be a jME method to set the native texture
                 * image format. (currently always GL11.GL_UNSIGNED_BYTE)
                 * Using formats with the same packing size will speed up
                 * texture updates.
                 */
                // TODO: this.setFormat(GL12.GL_UNSIGNED_SHORT_1_5_5_5);
                dataformat = GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV;
                log.info("texture format: GL_UNSIGNED_SHORT_1_5_5_5_REV");
            } else {
                // TODO: this.setFormat(GL12.GL_INT_8_8_8_8_REV);
                dataformat = GL12.GL_UNSIGNED_INT_8_8_8_8_REV;
                log.info("texture format: GL_UNSIGNED_INT_8_8_8_8_REV");
            }

            break;
        case 3:
        case 4:
        default:
            dataformat = GL11.GL_UNSIGNED_BYTE;
            log.info("texture format: GL_UNSIGNED_BYTE");
        }

        if (format.getFlipped() == RGBFormat.FALSE) {
            flipped = true;
        }

        this.videowidth = videowidth;
        this.videoheight = videoheight;

        try {
            int size = Math.max(videoheight, videowidth);

            if (!FastMath.isPowerOfTwo(size)) {
                int newsize = 2;
                do {
                    newsize <<= 1;
                } while (newsize < size);
                size = newsize;
            }
            this.width = size;
            this.height = size;

            data.clear();
            data.add(ByteBuffer.allocateDirect(size * size * 4).order(ByteOrder.nativeOrder()));

            ready = true;
            inittexture = true;
        } catch (Exception e) {
            e.printStackTrace();
            failed = true;
        }
        synchronized (this) {
            this.notifyAll();
        }

    }

    /**
     * Sets the sound volume level.
     *
     * @param level
     *            the new sound volume level
     */
    public void setSoundVolumeLevel(float level) {
        if (jmfplayer.getGainControl() != null) {
            jmfplayer.getGainControl().setLevel(level);
        }
    }

    /**
     * Sets the speed.
     *
     * @param rate
     *            the new speed
     */
    public void setSpeed(float rate) {
        jmfplayer.setRate(rate);
    }

    /**
     * Sets the time.
     *
     * @param time
     *            the new time
     */
    public void setTime(Time time) {
        jmfplayer.setMediaTime(time);
    }

    /**
     * Start movie.
     */
    public void startMovie() {
        active = true;
        jmfplayer.start();
    }

    /**
     * Starts from.
     *
     * @param seconds
     *            the seconds
     */
    public void startsFrom(final double seconds) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                jmfplayer.setMediaTime(new Time(seconds));

            }
        }).start();
    }

    /**
     * Stop movie.
     */
    synchronized public void stopMovie() {
        active = false;
        try {
            jmfplayer.stop();
            // jmfplayer.close();
        } catch (Exception ignored) {
            ignored.printStackTrace();
        }
    }

    /**
     * Update.
     *
     * @param texture
     *            the texture
     * @return true, if successful
     */
    public boolean update(Texture texture) {
        return update(texture, false);
    }

    /**
     * Update.
     *
     * @param texture
     *            Texture to update
     * @param syncToFrameRate
     *            Wait till the frame is updated before updating the texture.
     * @return true if the texture was updated
     */
    public boolean update(Texture texture, boolean syncToFrameRate) {
        if (!active) {
            return false;
        }
        if (syncToFrameRate) {
            synchronized (this) {
                while (lastupdated >= framecounter) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
        }

        if (lastupdated >= framecounter) {
            return false;
        }

        if ((lastupdated + 1) < framecounter) {
            log.info("missed frames: " + ((framecounter - lastupdated) + 1));
        }

        if (data == null) {
            return false;
        }

        if (inittexture && (this.scalemethod > 0)) {
            inittexture = false;
            // clear current textures.
            TextureState state = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
            state.setEnabled(true);
            state.apply();

            log.info("rescaling texture");

            if (scalemethod == SCALE_FIT) {
                float scale;
                if (this.videowidth > this.videoheight) {
                    scale = videowidth * (1f / this.width);
                } else {
                    scale = videoheight * (1f / this.height);
                }

                texture.setScale(new Vector3f(scale, scale, scale));
            }

            if (scalemethod == SCALE_MAXIMIZE) {
                // texture.setScale(new Vector3f(1f,videowidth* (1f /
                // this.width), videoheight * (1f / this.height)));
                texture.setScale(
                        new Vector3f(videowidth * (1f / this.width), videoheight * (1f / this.height), 1f));
            }

        }

        synchronized (this) {

            // long d = System.currentTimeMillis();

            GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture.getTextureId());
            // TODO: use this.dataformat
            GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, videowidth, videoheight, pixelformat, dataformat,
                    buffer);

            try {
                Util.checkGLError();
            } catch (OpenGLException e) {
                log.info("Error rendering video to texture. No glTexSubImage2D/OpenGL 1.2 support?");
            }

            lastupdated = framecounter;
            this.notifyAll();
        }
        return false;

    }

    /**
     * Wait some.
     *
     * @param time
     *            the time
     */
    public synchronized void waitSome(int time) {
        try {
            this.wait(time);
        } catch (InterruptedException e) {
        }
    }
}