package jogamp.opengl.av;
import java.io.IOException;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.media.opengl.GL;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLES2;
import javax.media.opengl.GLException;
import com.jogamp.opengl.av.GLMediaPlayer;
import com.jogamp.opengl.av.GLMediaEventListener;
import com.jogamp.opengl.util.texture.Texture;
/**
* After object creation an implementation may customize the behavior:
*
* - {@link #setTextureCount(int)}
* - {@link #setTextureTarget(int)}
* - {@link EGLMediaPlayerImpl#setEGLTexImageAttribs(boolean, boolean)}.
*
*
*
* See {@link GLMediaPlayer}.
*
*/
public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
protected State state;
protected int textureCount;
protected int textureTarget;
protected int[] texMinMagFilter = { GL.GL_NEAREST, GL.GL_NEAREST };
protected int[] texWrapST = { GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE };
protected URLConnection urlConn = null;
protected float playSpeed = 1.0f;
/** Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected int width = 0;
/** Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected int height = 0;
/** Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected int fps = 0;
/** Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected long bps = 0;
/** In frames. Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected long totalFrames = 0;
/** In ms. Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected long duration = 0;
/** Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected String acodec = null;
/** Shall be set by the {@link #initStreamImplPreGL()} method implementation. */
protected String vcodec = null;
protected long frameNumber = 0;
private TextureFrame[] texFrames = null;
protected HashMap texFrameMap = new HashMap();
private ArrayList eventListeners = new ArrayList();
protected GLMediaPlayerImpl() {
this.textureCount=3;
this.textureTarget=GL.GL_TEXTURE_2D;
this.state = State.UninitializedStream;
}
protected final void setTextureCount(int textureCount) {
this.textureCount=textureCount;
}
public final int getTextureCount() { return textureCount; }
protected final void setTextureTarget(int textureTarget) {
this.textureTarget=textureTarget;
}
public final int getTextureTarget() { return textureTarget; }
public final void setTextureMinMagFilter(int[] minMagFilter) { texMinMagFilter[0] = minMagFilter[0]; texMinMagFilter[1] = minMagFilter[1];}
public final int[] getTextureMinMagFilter() { return texMinMagFilter; }
public final void setTextureWrapST(int[] wrapST) { texWrapST[0] = wrapST[0]; texWrapST[1] = wrapST[1];}
public final int[] getTextureWrapST() { return texWrapST; }
public final State start() {
switch(state) {
case Stopped:
case Paused:
if(startImpl()) {
state = State.Playing;
}
}
return state;
}
protected abstract boolean startImpl();
public final State pause() {
if(State.Playing == state && pauseImpl()) {
state = State.Paused;
}
return state;
}
protected abstract boolean pauseImpl();
public final State stop() {
switch(state) {
case Playing:
case Paused:
if(stopImpl()) {
state = State.Stopped;
}
}
return state;
}
protected abstract boolean stopImpl();
public final long seek(long msec) {
final long cp;
switch(state) {
case Stopped:
case Playing:
case Paused:
cp = seekImpl(msec);
break;
default:
cp = 0;
}
return cp;
}
protected abstract long seekImpl(long msec);
public final State getState() { return state; }
@Override
public final State initStream(URLConnection urlConn) throws IllegalStateException, IOException {
if(State.UninitializedStream != state) {
throw new IllegalStateException("Instance not in state "+State.UninitializedStream+", but "+state);
}
this.urlConn = urlConn;
if (this.urlConn != null) {
initStreamImplPreGL();
state = State.UninitializedGL;
}
return state;
}
/**
* Implementation shall set the following set of data here or at {@link #setStreamImplPostGL()}
* @see #width
* @see #height
* @see #fps
* @see #bps
* @see #totalFrames
* @see #acodec
* @see #vcodec
*/
protected abstract void initStreamImplPreGL() throws IOException;
@Override
public final State initGL(GL gl) throws IllegalStateException, GLException {
if(State.UninitializedGL != state) {
throw new IllegalStateException("Instance not in state "+State.UninitializedGL+", but "+state);
}
final GLContext ctx = gl.getContext();
if(!ctx.isCurrent()) {
throw new GLException("Not current: "+ctx);
}
try {
if(null!=texFrames) {
removeAllImageTextures(ctx);
} else {
texFrames = new TextureFrame[textureCount];
}
final int[] tex = new int[textureCount];
{
gl.glGenTextures(textureCount, tex, 0);
final int err = gl.glGetError();
if( GL.GL_NO_ERROR != err ) {
throw new RuntimeException("TextureNames creation failed (num: "+textureCount+"): err "+toHexString(err));
}
}
for(int i=0; i tex[idx] ) {
throw new RuntimeException("TextureName "+toHexString(tex[idx])+" invalid.");
}
gl.glBindTexture(textureTarget, tex[idx]);
{
final int err = gl.glGetError();
if( GL.GL_NO_ERROR != err ) {
throw new RuntimeException("Couldn't bind textureName "+toHexString(tex[idx])+" to 2D target, err "+toHexString(err));
}
}
if(GLES2.GL_TEXTURE_EXTERNAL_OES != textureTarget) {
// create space for buffer with a texture
gl.glTexImage2D(
textureTarget, // target
0, // level
GL.GL_RGBA, // internal format
width, // width
height, // height
0, // border
GL.GL_RGBA, // format
GL.GL_UNSIGNED_BYTE, // type
null); // pixels -- will be provided later
{
final int err = gl.glGetError();
if( GL.GL_NO_ERROR != err ) {
throw new RuntimeException("Couldn't create TexImage2D RGBA "+width+"x"+height+", err "+toHexString(err));
}
}
}
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, texMinMagFilter[0]);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, texMinMagFilter[1]);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, texWrapST[0]);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, texWrapST[1]);
return com.jogamp.opengl.util.texture.TextureIO.newTexture(tex[idx],
textureTarget,
width, height,
width, height,
mustFlipVertically);
}
protected void destroyTexImage(GLContext ctx, TextureFrame imgTex) {
imgTex.getTexture().destroy(ctx.getGL());
}
protected void removeAllImageTextures(GLContext ctx) {
if(null != texFrames) {
for(int i=0; i i = eventListeners.iterator(); i.hasNext(); ) {
i.next().attributesChanges(this, event_mask);
}
}
}
protected void newFrameAvailable(TextureFrame frame) {
frameNumber++;
synchronized(eventListenersLock) {
for(Iterator i = eventListeners.iterator(); i.hasNext(); ) {
i.next().newFrameAvailable(this, frame);
}
}
}
@Override
public synchronized float getPlaySpeed() {
return playSpeed;
}
@Override
public synchronized State destroy(GL gl) {
destroyImpl(gl);
removeAllImageTextures(gl.getContext());
state = State.UninitializedStream;
return state;
}
protected abstract void destroyImpl(GL gl);
@Override
public synchronized URLConnection getURLConnection() {
return urlConn;
}
@Override
public synchronized String getVideoCodec() {
return vcodec;
}
@Override
public synchronized String getAudioCodec() {
return acodec;
}
@Override
public synchronized long getTotalFrames() {
return totalFrames;
}
@Override
public synchronized long getDuration() {
return duration;
}
@Override
public synchronized long getBitrate() {
return bps;
}
@Override
public synchronized int getFramerate() {
return fps;
}
@Override
public synchronized int getWidth() {
return width;
}
@Override
public synchronized int getHeight() {
return height;
}
@Override
public synchronized String toString() {
final float ct = getCurrentPosition() / 1000.0f, tt = getDuration() / 1000.0f;
return "GLMediaPlayer ["+state+", "+frameNumber+"/"+totalFrames+" frames, "+ct+"/"+tt+"s, stream [video ["+vcodec+", "+width+"x"+height+", "+fps+"fps, "+bps+"bsp], "+urlConn.getURL().toExternalForm()+"]]";
}
@Override
public void addEventListener(GLMediaEventListener l) {
if(l == null) {
return;
}
synchronized(eventListenersLock) {
eventListeners.add(l);
}
}
@Override
public void removeEventListener(GLMediaEventListener l) {
if (l == null) {
return;
}
synchronized(eventListenersLock) {
eventListeners.remove(l);
}
}
@Override
public synchronized GLMediaEventListener[] getEventListeners() {
synchronized(eventListenersLock) {
return eventListeners.toArray(new GLMediaEventListener[eventListeners.size()]);
}
}
private Object eventListenersLock = new Object();
protected static final String toHexString(long v) {
return "0x"+Long.toHexString(v);
}
protected static final String toHexString(int v) {
return "0x"+Integer.toHexString(v);
}
}