/** * Copyright 2012 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.opengl.test.junit.jogl.demos.es2.av; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.FloatBuffer; import com.jogamp.opengl.GL; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLAnimatorControl; import com.jogamp.opengl.GLAutoDrawable; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLES2; import com.jogamp.opengl.GLEventListener; import com.jogamp.opengl.GLException; import com.jogamp.opengl.GLProfile; import com.jogamp.opengl.GLUniformData; import com.jogamp.opengl.fixedfunc.GLMatrixFunc; import com.jogamp.common.net.Uri; import com.jogamp.common.os.Platform; import com.jogamp.common.util.InterruptSource; import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.font.Font; import com.jogamp.junit.util.JunitTracer; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyAdapter; import com.jogamp.newt.event.KeyEvent; import com.jogamp.newt.event.KeyListener; import com.jogamp.newt.event.MouseAdapter; import com.jogamp.newt.event.MouseEvent; import com.jogamp.newt.event.MouseListener; import com.jogamp.newt.event.WindowAdapter; import com.jogamp.newt.event.WindowEvent; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.GLExtensions; import com.jogamp.opengl.JoglVersion; import com.jogamp.opengl.test.junit.graph.TextRendererGLELBase; import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.GLArrayDataServer; import com.jogamp.opengl.util.PMVMatrix; import com.jogamp.opengl.util.av.GLMediaPlayer; import com.jogamp.opengl.util.av.GLMediaPlayer.GLMediaEventListener; import com.jogamp.opengl.util.av.GLMediaPlayer.StreamException; import com.jogamp.opengl.util.av.GLMediaPlayerFactory; import com.jogamp.opengl.util.glsl.ShaderCode; import com.jogamp.opengl.util.glsl.ShaderProgram; import com.jogamp.opengl.util.glsl.ShaderState; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureCoords; import com.jogamp.opengl.util.texture.TextureSequence; import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame; /** * Simple planar movie player w/ orthogonal 1:1 projection. */ public class MovieSimple implements GLEventListener { public static final int EFFECT_NORMAL = 0; public static final int EFFECT_GRADIENT_BOTTOM2TOP = 1<<1; public static final int EFFECT_TRANSPARENT = 1<<3; public static final String WINDOW_KEY = "window"; public static final String PLAYER = "player"; private static boolean waitForKey = false; private int surfWidth, surfHeight; private int prevMouseX; // , prevMouseY; private int rotate = 0; private boolean orthoProjection = true; private float nearPlaneNormalized; private float zoom0; private float zoom1; private float zoom; private long startTime; private int effects = EFFECT_NORMAL; private float alpha = 1.0f; private int swapInterval = 1; private boolean swapIntervalSet = true; private GLMediaPlayer mPlayer; private final boolean mPlayerShared; private boolean mPlayerScaleOrig; private float[] verts = null; private GLArrayDataServer interleavedVBO; private volatile boolean resetGLState = false; private ShaderState st; private PMVMatrix pmvMatrix; private GLUniformData pmvMatrixUniform; private static final String shaderBasename = "texsequence_xxx"; private static final String myTextureLookupName = "myTexture2D"; /** Blender's Big Buck Bunny: 24f 416p H.264, AAC 48000 Hz, 2 ch, mpeg stream. */ public static final Uri defURI; static { Uri _defURI = null; try { // Blender's Big Buck Bunny Trailer: 24f 640p VP8, Vorbis 44100Hz mono, WebM/Matroska Stream. // _defURI = new URI("http://video.webmfiles.org/big-buck-bunny_trailer.webm"); _defURI = Uri.cast("http://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"); } catch (final URISyntaxException e) { e.printStackTrace(); } defURI = _defURI; } final int[] textSampleCount = { 4 }; private final class InfoTextRendererGLELBase extends TextRendererGLELBase { private final Font font = getFont(0, 0, 0); private final float fontSize = 10f; private final GLRegion regionFPS; InfoTextRendererGLELBase(final int rmode, final boolean lowPerfDevice) { // FIXME: Graph TextRenderer does not AA well w/o MSAA and FBO super(rmode, textSampleCount); this.setRendererCallbacks(RegionRenderer.defaultBlendEnable, RegionRenderer.defaultBlendDisable); if( lowPerfDevice ) { regionFPS = null; } else { regionFPS = GLRegion.create(renderModes, null); System.err.println("RegionFPS "+Region.getRenderModeString(renderModes)+", sampleCount "+textSampleCount[0]+", class "+regionFPS.getClass().getName()); } staticRGBAColor[0] = 0.9f; staticRGBAColor[1] = 0.9f; staticRGBAColor[2] = 0.9f; staticRGBAColor[3] = 1.0f; } @Override public void init(final GLAutoDrawable drawable) { super.init(drawable); } @Override public void dispose(final GLAutoDrawable drawable) { if( null != regionFPS ) { regionFPS.destroy(drawable.getGL().getGL2ES2()); } super.dispose(drawable); } @Override public void display(final GLAutoDrawable drawable) { final GLAnimatorControl anim = drawable.getAnimator(); final float lfps = null != anim ? anim.getLastFPS() : 0f; final float tfps = null != anim ? anim.getTotalFPS() : 0f; final boolean hasVideo = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID(); final float pts = ( hasVideo ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS() ) / 1000f; // Note: MODELVIEW is from [ 0 .. height ] final int height = drawable.getSurfaceHeight(); final float aspect = (float)mPlayer.getWidth() / (float)mPlayer.getHeight(); final String ptsPrec = null != regionFPS ? "3.1" : "3.0"; final String text1 = String.format("%0"+ptsPrec+"f/%0"+ptsPrec+"f s, %s (%01.2fx, vol %01.2f), a %01.2f, fps %02.1f -> %02.1f / %02.1f, v-sync %b", pts, mPlayer.getDuration() / 1000f, mPlayer.getState().toString().toLowerCase(), mPlayer.getPlaySpeed(), mPlayer.getAudioVolume(), aspect, mPlayer.getFramerate(), lfps, tfps, swapIntervalSet); final String text2 = String.format("audio: id %d, kbps %d, codec %s", mPlayer.getAID(), mPlayer.getAudioBitrate()/1000, mPlayer.getAudioCodec()); final String text3 = String.format("video: id %d, kbps %d, codec %s", mPlayer.getVID(), mPlayer.getVideoBitrate()/1000, mPlayer.getVideoCodec()); final String text4 = mPlayer.getUri().path.decode(); if( displayOSD && null != renderer ) { // We share ClearColor w/ MovieSimple's init ! final float pixelSize = font.getPixelSize(fontSize, dpiH); if( null != regionFPS ) { renderString(drawable, font, pixelSize, text1, 1 /* col */, 1 /* row */, 0, 0, -1, regionFPS); // no-cache } else { renderString(drawable, font, pixelSize, text1, 1 /* col */, 1 /* row */, 0, 0, -1, true); } renderString(drawable, font, pixelSize, text2, 1 /* col */, -4 /* row */, 0, height, -1, true); renderString(drawable, font, pixelSize, text3, 1 /* col */, -3 /* row */, 0, height, -1, true); renderString(drawable, font, pixelSize, text4, 1 /* col */, -2 /* row */, 0, height, -1, true); } } }; private InfoTextRendererGLELBase textRendererGLEL = null; private boolean displayOSD = true; private final MouseListener mouseAction = new MouseAdapter() { public void mousePressed(final MouseEvent e) { if(e.getY()<=surfHeight/2 && null!=mPlayer && 1 == e.getClickCount()) { if(GLMediaPlayer.State.Playing == mPlayer.getState()) { mPlayer.pause(false); } else { mPlayer.play(); } } } public void mouseReleased(final MouseEvent e) { if(e.getY()<=surfHeight/2) { rotate = -1; zoom = zoom0; System.err.println("zoom: "+zoom); } } public void mouseMoved(final MouseEvent e) { prevMouseX = e.getX(); // prevMouseY = e.getY(); } public void mouseDragged(final MouseEvent e) { final int x = e.getX(); final int y = e.getY(); if(y>surfHeight/2) { final float dp = (float)(x-prevMouseX)/(float)surfWidth; final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); mPlayer.seek(pts0 + (int) (mPlayer.getDuration() * dp)); } else { mPlayer.play(); rotate = 1; zoom = zoom1; } prevMouseX = x; // prevMouseY = y; } public void mouseWheelMoved(final MouseEvent e) { if( !e.isShiftDown() ) { zoom += e.getRotation()[1]/10f; // vertical: wheel System.err.println("zoom: "+zoom); } } }; private final KeyListener keyAction = new KeyAdapter() { public void keyReleased(final KeyEvent e) { if( e.isAutoRepeat() ) { return; } System.err.println("MC "+e); final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); int pts1 = 0; switch(e.getKeySymbol()) { case KeyEvent.VK_V: { switch(swapInterval) { case 0: swapInterval = -1; break; case -1: swapInterval = 1; break; case 1: swapInterval = 0; break; default: swapInterval = 1; break; } swapIntervalSet = true; break; } case KeyEvent.VK_O: displayOSD = !displayOSD; break; case KeyEvent.VK_RIGHT: pts1 = pts0 + 1000; break; case KeyEvent.VK_UP: pts1 = pts0 + 10000; break; case KeyEvent.VK_PAGE_UP: pts1 = pts0 + 30000; break; case KeyEvent.VK_LEFT: pts1 = pts0 - 1000; break; case KeyEvent.VK_DOWN: pts1 = pts0 - 10000; break; case KeyEvent.VK_PAGE_DOWN: pts1 = pts0 - 30000; break; case KeyEvent.VK_ESCAPE: case KeyEvent.VK_HOME: case KeyEvent.VK_BACK_SPACE: { mPlayer.seek(0); break; } case KeyEvent.VK_SPACE: { if(GLMediaPlayer.State.Paused == mPlayer.getState()) { mPlayer.play(); } else { mPlayer.pause(false); } break; } case KeyEvent.VK_MULTIPLY: mPlayer.setPlaySpeed(1.0f); break; case KeyEvent.VK_SUBTRACT: { float playSpeed = mPlayer.getPlaySpeed(); if( e.isShiftDown() ) { playSpeed /= 2.0f; } else { playSpeed -= 0.1f; } mPlayer.setPlaySpeed(playSpeed); } break; case KeyEvent.VK_ADD: { float playSpeed = mPlayer.getPlaySpeed(); if( e.isShiftDown() ) { playSpeed *= 2.0f; } else { playSpeed += 0.1f; } mPlayer.setPlaySpeed(playSpeed); } break; case KeyEvent.VK_M: { float audioVolume = mPlayer.getAudioVolume(); if( audioVolume > 0.5f ) { audioVolume = 0f; } else { audioVolume = 1f; } mPlayer.setAudioVolume(audioVolume); } break; } if( 0 != pts1 ) { mPlayer.seek(pts1); } } }; /** * Default constructor which also issues {@link #initStream(URI, int, int, int)} w/ default values * and polls until the {@link GLMediaPlayer} is {@link GLMediaPlayer.State#Initialized}. * If {@link GLMediaEventListener#EVENT_CHANGE_EOS} is reached, the stream is started over again. * <p> * This default constructor is merely useful for some <i>drop-in</i> test, e.g. using an applet. * </p> */ public MovieSimple() { this(null); mPlayer.addEventListener(new GLMediaEventListener() { @Override public void newFrameAvailable(final GLMediaPlayer ts, final TextureFrame newFrame, final long when) { } @Override public void attributesChanged(final GLMediaPlayer mp, final int event_mask, final long when) { System.err.println("MovieCube AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); System.err.println("MovieCube State: "+mp); if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) ) { resetGLState(); } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { new InterruptSource.Thread() { public void run() { // loop for-ever .. mPlayer.seek(0); mPlayer.play(); } }.start(); } } }); initStream(defURI, GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO, 3 /* textureCount */); StreamException se = null; while( null == se && GLMediaPlayer.State.Initialized != mPlayer.getState() ) { try { Thread.sleep(16); } catch (final InterruptedException e) { } se = mPlayer.getStreamException(); } if( null != se ) { se.printStackTrace(); throw new RuntimeException(se); } } /** Custom constructor, user needs to issue {@link #initStream(URI, int, int, int)} afterwards. */ public MovieSimple(final GLMediaPlayer sharedMediaPlayer) throws IllegalStateException { mPlayer = sharedMediaPlayer; mPlayerScaleOrig = false; mPlayerShared = null != mPlayer; if( !mPlayerShared ) { mPlayer = GLMediaPlayerFactory.createDefault(); mPlayer.attachObject(PLAYER, this); } System.out.println("pC.1a shared "+mPlayerShared+", "+mPlayer); } public void initStream(final Uri streamLoc, final int vid, final int aid, final int textureCount) { mPlayer.initStream(streamLoc, vid, aid, textureCount); System.out.println("pC.1b "+mPlayer); } public void setSwapInterval(final int v) { this.swapInterval = v; } public GLMediaPlayer getGLMediaPlayer() { return mPlayer; } public void setScaleOrig(final boolean v) { mPlayerScaleOrig = v; } /** defaults to true */ public void setOrthoProjection(final boolean v) { orthoProjection=v; } public boolean getOrthoProjection() { return orthoProjection; } public boolean hasEffect(final int e) { return 0 != ( effects & e ) ; } public void setEffects(final int e) { effects = e; }; public void setTransparency(final float alpha) { this.effects |= EFFECT_TRANSPARENT; this.alpha = alpha; } public void resetGLState() { resetGLState = true; } private void initShader(final GL2ES2 gl) { // Create & Compile the shader objects final ShaderCode rsVp = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, MovieSimple.class, "../shader", "../shader/bin", shaderBasename, true); final ShaderCode rsFp = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, MovieSimple.class, "../shader", "../shader/bin", shaderBasename, true); boolean preludeGLSLVersion = true; if( GLES2.GL_TEXTURE_EXTERNAL_OES == mPlayer.getTextureTarget() ) { if( !gl.isExtensionAvailable(GLExtensions.OES_EGL_image_external) ) { throw new GLException(GLExtensions.OES_EGL_image_external+" requested but not available"); } if( Platform.OSType.ANDROID == Platform.getOSType() && gl.isGLES3() ) { // Bug on Nexus 10, ES3 - Android 4.3, where // GL_OES_EGL_image_external extension directive leads to a failure _with_ '#version 300 es' ! // P0003: Extension 'GL_OES_EGL_image_external' not supported preludeGLSLVersion = false; } } rsVp.defaultShaderCustomization(gl, preludeGLSLVersion, true); int rsFpPos = preludeGLSLVersion ? rsFp.addGLSLVersion(gl) : 0; rsFpPos = rsFp.insertShaderSource(0, rsFpPos, mPlayer.getRequiredExtensionsShaderStub()); rsFp.addDefaultShaderPrecision(gl, rsFpPos); final String texLookupFuncName = mPlayer.getTextureLookupFunctionName(myTextureLookupName); rsFp.replaceInShaderSource(myTextureLookupName, texLookupFuncName); // Inject TextureSequence shader details final StringBuilder sFpIns = new StringBuilder(); sFpIns.append("uniform ").append(mPlayer.getTextureSampler2DType()).append(" mgl_ActiveTexture;\n"); sFpIns.append(mPlayer.getTextureLookupFragmentShaderImpl()); rsFp.insertShaderSource(0, "TEXTURE-SEQUENCE-CODE-BEGIN", 0, sFpIns); // Create & Link the shader program final ShaderProgram sp = new ShaderProgram(); sp.add(rsVp); sp.add(rsFp); if(!sp.link(gl, System.err)) { throw new GLException("Couldn't link program: "+sp); } // Let's manage all our states using ShaderState. st = new ShaderState(); st.attachShaderProgram(gl, sp, false); } @Override public void init(final GLAutoDrawable drawable) { if(null == mPlayer) { throw new InternalError("mPlayer null"); } if( GLMediaPlayer.State.Uninitialized == mPlayer.getState() ) { throw new IllegalStateException("mPlayer in uninitialized state: "+mPlayer); } final boolean hasVideo = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID(); resetGLState = false; zoom0 = orthoProjection ? 0f : -2.5f; zoom1 = orthoProjection ? 0f : -5f; zoom = zoom0; final GL2ES2 gl = drawable.getGL().getGL2ES2(); System.err.println(JoglVersion.getGLInfo(gl, null)); System.err.println("Alpha: "+alpha+", opaque "+drawable.getChosenGLCapabilities().isBackgroundOpaque()+ ", "+drawable.getClass().getName()+", "+drawable); if(waitForKey) { JunitTracer.waitForKey("Init>"); } final Texture tex; try { System.out.println("p0 "+mPlayer+", shared "+mPlayerShared); if(!mPlayerShared && GLMediaPlayer.State.Initialized == mPlayer.getState() ) { mPlayer.initGL(gl); } System.out.println("p1 "+mPlayer+", shared "+mPlayerShared); final TextureFrame frame = mPlayer.getLastTexture(); if( null != frame ) { if( !hasVideo ) { throw new InternalError("XXX: "+mPlayer); } tex = frame.getTexture(); if( null == tex ) { throw new InternalError("XXX: "+mPlayer); } } else { tex = null; if( hasVideo ) { throw new InternalError("XXX: "+mPlayer); } } if(!mPlayerShared) { mPlayer.setTextureMinMagFilter( new int[] { GL.GL_NEAREST, GL.GL_LINEAR } ); } } catch (final Exception glex) { glex.printStackTrace(); if(!mPlayerShared && null != mPlayer) { mPlayer.destroy(gl); mPlayer = null; } throw new GLException(glex); } if( hasVideo ) { initShader(gl); // Push the 1st uniform down the path st.useProgram(gl, true); final int[] viewPort = new int[] { 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()}; pmvMatrix = new PMVMatrix(); reshapePMV(viewPort[2], viewPort[3]); pmvMatrixUniform = new GLUniformData("mgl_PMVMatrix", 4, 4, pmvMatrix.glGetPMvMatrixf()); if(!st.uniform(gl, pmvMatrixUniform)) { throw new GLException("Error setting PMVMatrix in shader: "+st); } if(!st.uniform(gl, new GLUniformData("mgl_ActiveTexture", mPlayer.getTextureUnit()))) { throw new GLException("Error setting mgl_ActiveTexture in shader: "+st); } final float dWidth = drawable.getSurfaceWidth(); final float dHeight = drawable.getSurfaceHeight(); final float mWidth = mPlayer.getWidth(); final float mHeight = mPlayer.getHeight(); final float mAspect = mWidth/mHeight; System.err.println("XXX0: mov aspect: "+mAspect); float xs, ys; if(orthoProjection) { if(mPlayerScaleOrig && mWidth < dWidth && mHeight < dHeight) { xs = mWidth/2f; ys = xs / mAspect; } else { xs = dWidth/2f; ys = xs / mAspect; // w>h } } else { if(mPlayerScaleOrig && mWidth < dWidth && mHeight < dHeight) { xs = mAspect * ( mWidth / dWidth ) ; ys = xs / mAspect ; } else { xs = mAspect; ys = 1f; // b>h } } verts = new float[] { -1f*xs, -1f*ys, 0f, // LB 1f*xs, 1f*ys, 0f // RT }; { System.err.println("XXX0: pixel LB: "+verts[0]+", "+verts[1]+", "+verts[2]); System.err.println("XXX0: pixel RT: "+verts[3]+", "+verts[4]+", "+verts[5]); final float[] winLB = new float[3]; final float[] winRT = new float[3]; pmvMatrix.gluProject(verts[0], verts[1], verts[2], viewPort, 0, winLB, 0); pmvMatrix.gluProject(verts[3], verts[4], verts[5], viewPort, 0, winRT, 0); System.err.println("XXX0: win LB: "+winLB[0]+", "+winLB[1]+", "+winLB[2]); System.err.println("XXX0: win RT: "+winRT[0]+", "+winRT[1]+", "+winRT[2]); } interleavedVBO = GLArrayDataServer.createGLSLInterleaved(3+4+2, GL.GL_FLOAT, false, 3*4, GL.GL_STATIC_DRAW); { interleavedVBO.addGLSLSubArray("mgl_Vertex", 3, GL.GL_ARRAY_BUFFER); interleavedVBO.addGLSLSubArray("mgl_Color", 4, GL.GL_ARRAY_BUFFER); interleavedVBO.addGLSLSubArray("mgl_MultiTexCoord", 2, GL.GL_ARRAY_BUFFER); } updateInterleavedVBO(gl, tex); st.ownAttribute(interleavedVBO, true); gl.glClearColor(0.3f, 0.3f, 0.3f, 0.3f); gl.glEnable(GL.GL_DEPTH_TEST); st.useProgram(gl, false); // Let's show the completed shader state .. System.out.println("iVBO: "+interleavedVBO); System.out.println(st); } if(!mPlayerShared) { mPlayer.play(); System.out.println("play.0 "+mPlayer); } startTime = System.currentTimeMillis(); final Object upstreamWidget = drawable.getUpstreamWidget(); if (upstreamWidget instanceof Window) { final Window window = (Window) upstreamWidget; window.addMouseListener(mouseAction); window.addKeyListener(keyAction); surfWidth = window.getSurfaceWidth(); surfHeight = window.getSurfaceHeight(); } final int rmode = drawable.getChosenGLCapabilities().getSampleBuffers() ? 0 : Region.VBAA_RENDERING_BIT; final boolean lowPerfDevice = gl.isGLES(); textRendererGLEL = new InfoTextRendererGLELBase(rmode, lowPerfDevice); drawable.addGLEventListener(textRendererGLEL); } protected void updateInterleavedVBO(final GL gl, final Texture tex) { final float ss = 1f, ts = 1f; // scale tex-coord final boolean wasEnabled = interleavedVBO.enabled(); interleavedVBO.seal(gl, false); interleavedVBO.rewind(); { final FloatBuffer ib = (FloatBuffer)interleavedVBO.getBuffer(); final TextureCoords tc = tex.getImageTexCoords(); System.err.println("XXX0: "+tc); System.err.println("XXX0: tex aspect: "+tex.getAspectRatio()); System.err.println("XXX0: tex y-flip: "+tex.getMustFlipVertically()); // left-bottom ib.put(verts[0]); ib.put(verts[1]); ib.put(verts[2]); if( hasEffect(EFFECT_GRADIENT_BOTTOM2TOP) ) { ib.put( 0); ib.put( 0); ib.put( 0); ib.put(alpha); } else { ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); } ib.put( tc.left() *ss); ib.put( tc.bottom() *ts); // right-bottom ib.put(verts[3]); ib.put(verts[1]); ib.put(verts[2]); if( hasEffect(EFFECT_GRADIENT_BOTTOM2TOP) ) { ib.put( 0); ib.put( 0); ib.put( 0); ib.put(alpha); } else { ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); } ib.put( tc.right() *ss); ib.put( tc.bottom() *ts); // left-top ib.put(verts[0]); ib.put(verts[4]); ib.put(verts[2]); ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); ib.put( tc.left() *ss); ib.put( tc.top() *ts); // right-top ib.put(verts[3]); ib.put(verts[4]); ib.put(verts[2]); ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); ib.put( tc.right() *ss); ib.put( tc.top() *ts); } interleavedVBO.seal(gl, true); if( !wasEnabled ) { interleavedVBO.enableBuffer(gl, false); } } @Override public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { final GL2ES2 gl = drawable.getGL().getGL2ES2(); if(null == mPlayer) { return; } surfWidth = width; surfHeight = height; if(null != st) { reshapePMV(width, height); st.useProgram(gl, true); st.uniform(gl, pmvMatrixUniform); st.useProgram(gl, false); } System.out.println("pR "+mPlayer); } private final float zNear = 1f; private final float zFar = 10f; private void reshapePMV(final int width, final int height) { pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); pmvMatrix.glLoadIdentity(); if(orthoProjection) { final float fw = width / 2f; final float fh = height/ 2f; pmvMatrix.glOrthof(-fw, fw, -fh, fh, -1.0f, 1.0f); nearPlaneNormalized = 0f; } else { pmvMatrix.gluPerspective(45.0f, (float)width / (float)height, zNear, zFar); nearPlaneNormalized = 1f/(10f-1f); } System.err.println("XXX0: Perspective nearPlaneNormalized: "+nearPlaneNormalized); pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmvMatrix.glLoadIdentity(); pmvMatrix.glTranslatef(0, 0, zoom0); } @Override public void dispose(final GLAutoDrawable drawable) { drawable.disposeGLEventListener(textRendererGLEL, true); textRendererGLEL = null; disposeImpl(drawable, true); } private void disposeImpl(final GLAutoDrawable drawable, final boolean disposePlayer) { if(null == mPlayer) { return; } final Object upstreamWidget = drawable.getUpstreamWidget(); if (upstreamWidget instanceof Window) { final Window window = (Window) upstreamWidget; window.removeMouseListener(mouseAction); window.removeKeyListener(keyAction); } System.out.println("pD.1 "+mPlayer+", disposePlayer "+disposePlayer); final GL2ES2 gl = drawable.getGL().getGL2ES2(); if( disposePlayer ) { if(!mPlayerShared) { mPlayer.destroy(gl); } System.out.println("pD.X "+mPlayer); mPlayer=null; } pmvMatrixUniform = null; if(null != pmvMatrix) { pmvMatrix=null; } if(null != st) { st.destroy(gl); st=null; } } long lastPerfPos = 0; @Override public void display(final GLAutoDrawable drawable) { final GL2ES2 gl = drawable.getGL().getGL2ES2(); if( swapIntervalSet ) { final int _swapInterval = swapInterval; gl.setSwapInterval(_swapInterval); // in case switching the drawable (impl. may bound attribute there) drawable.getAnimator().resetFPSCounter(); swapInterval = gl.getSwapInterval(); System.err.println("Swap Interval: "+_swapInterval+" -> "+swapInterval); swapIntervalSet = false; } if(null == mPlayer) { return; } if( resetGLState ) { resetGLState = false; System.err.println("XXX resetGLState"); disposeImpl(drawable, false); init(drawable); reshape(drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); } final long currentPos = System.currentTimeMillis(); if( currentPos - lastPerfPos > 2000 ) { System.err.println( mPlayer.getPerfString() ); lastPerfPos = currentPos; } gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); if(null == st) { return; } st.useProgram(gl, true); pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmvMatrix.glLoadIdentity(); pmvMatrix.glTranslatef(0, 0, zoom); if(rotate > 0) { final float ang = ((System.currentTimeMillis() - startTime) * 360.0f) / 8000.0f; pmvMatrix.glRotatef(ang, 0, 0, 1); } else { rotate = 0; } st.uniform(gl, pmvMatrixUniform); interleavedVBO.enableBuffer(gl, true); Texture tex = null; if(null!=mPlayer) { final TextureSequence.TextureFrame texFrame; if( mPlayerShared ) { texFrame=mPlayer.getLastTexture(); } else { texFrame=mPlayer.getNextTexture(gl); } if(null != texFrame) { tex = texFrame.getTexture(); gl.glActiveTexture(GL.GL_TEXTURE0+mPlayer.getTextureUnit()); tex.enable(gl); tex.bind(gl); } } gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4); if(null != tex) { tex.disable(gl); } interleavedVBO.enableBuffer(gl, false); st.useProgram(gl, false); } static class MyGLMediaEventListener implements GLMediaEventListener { void destroyWindow(final Window window) { new InterruptSource.Thread() { public void run() { window.destroy(); } }.start(); } @Override public void newFrameAvailable(final GLMediaPlayer ts, final TextureFrame newFrame, final long when) { } @Override public void attributesChanged(final GLMediaPlayer mp, final int event_mask, final long when) { System.err.println("MovieSimple AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); System.err.println("MovieSimple State: "+mp); final GLWindow window = (GLWindow) mp.getAttachedObject(WINDOW_KEY); final MovieSimple ms = (MovieSimple)mp.getAttachedObject(PLAYER); if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) ) { System.err.println("MovieSimple State: CHANGE_SIZE"); if( origSize ) { window.setSurfaceSize(mp.getWidth(), mp.getHeight()); } // window.disposeGLEventListener(ms, false /* remove */ ); ms.resetGLState(); } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { System.err.println("MovieSimple State: INIT"); // Use GLEventListener in all cases [A+V, V, A] window.addGLEventListener(ms); final GLAnimatorControl anim = window.getAnimator(); anim.setUpdateFPSFrames(60, null); anim.resetFPSCounter(); /** * Kick off player w/o GLEventListener, i.e. for audio only. * new InterruptSource.Thread() { public void run() { try { mp.initGL(null); if ( GLMediaPlayer.State.Paused == mp.getState() ) { // init OK mp.play(); } System.out.println("play.1 "+mp); } catch (Exception e) { e.printStackTrace(); destroyWindow(); return; } } }.start(); */ } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_PLAY & event_mask ) ) { window.getAnimator().resetFPSCounter(); } boolean destroy = false; Throwable err = null; if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { err = ms.mPlayer.getStreamException(); if( null != err ) { System.err.println("MovieSimple State: EOS + Exception"); destroy = true; } else { System.err.println("MovieSimple State: EOS"); if( loopEOS ) { new InterruptSource.Thread() { public void run() { mp.setPlaySpeed(1f); mp.seek(0); mp.play(); } }.start(); } else { destroy = true; } } } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_ERR & event_mask ) ) { err = ms.mPlayer.getStreamException(); if( null != err ) { System.err.println("MovieSimple State: ERR + Exception"); } else { System.err.println("MovieSimple State: ERR"); } destroy = true; } if( destroy ) { if( null != err ) { err.printStackTrace(); } destroyWindow(window); } } }; public final static MyGLMediaEventListener myGLMediaEventListener = new MyGLMediaEventListener(); static boolean loopEOS = false; static boolean origSize; public static void main(final String[] args) throws IOException, URISyntaxException { int swapInterval = 1; int width = 800; int height = 600; int textureCount = 3; // default - threaded boolean ortho = true; boolean zoom = false; boolean forceES2 = false; boolean forceES3 = false; boolean forceGL3 = false; boolean forceGLDef = false; int vid = GLMediaPlayer.STREAM_ID_AUTO; int aid = GLMediaPlayer.STREAM_ID_AUTO; final int windowCount; { int _windowCount = 1; for(int i=0; i<args.length; i++) { if(args[i].equals("-windows")) { i++; _windowCount = MiscUtils.atoi(args[i], _windowCount); } } windowCount = _windowCount; } final String[] urls_s = new String[windowCount]; String file_s1=null, file_s2=null; { boolean _origSize = false; for(int i=0; i<args.length; i++) { if(args[i].equals("-vid")) { i++; vid = MiscUtils.atoi(args[i], vid); } else if(args[i].equals("-aid")) { i++; aid = MiscUtils.atoi(args[i], aid); } else if(args[i].equals("-width")) { i++; width = MiscUtils.atoi(args[i], width); } else if(args[i].equals("-height")) { i++; height = MiscUtils.atoi(args[i], height); } else if(args[i].equals("-osize")) { _origSize = true; } else if(args[i].equals("-textureCount")) { i++; textureCount = MiscUtils.atoi(args[i], textureCount); } else if(args[i].equals("-es2")) { forceES2 = true; } else if(args[i].equals("-es3")) { forceES3 = true; } else if(args[i].equals("-gl3")) { forceGL3 = true; } else if(args[i].equals("-gldef")) { forceGLDef = true; } else if(args[i].equals("-vsync")) { i++; swapInterval = MiscUtils.atoi(args[i], swapInterval); } else if(args[i].equals("-projection")) { ortho=false; } else if(args[i].equals("-zoom")) { zoom=true; } else if(args[i].equals("-loop")) { loopEOS=true; } else if(args[i].equals("-urlN")) { i++; final int n = MiscUtils.atoi(args[i], 0); i++; urls_s[n] = args[i]; } else if(args[i].equals("-url")) { i++; urls_s[0] = args[i]; } else if(args[i].equals("-file1")) { i++; file_s1 = args[i]; } else if(args[i].equals("-file2")) { i++; file_s2 = args[i]; } else if(args[i].equals("-wait")) { waitForKey = true; } } origSize = _origSize; } final Uri streamLoc0; if( null != urls_s[0] ) { streamLoc0 = Uri.cast( urls_s[0] ); } else if( null != file_s1 ) { final File movieFile = new File(file_s1); streamLoc0 = Uri.valueOf(movieFile); } else if( null != file_s2 ) { streamLoc0 = Uri.valueOf(new File(file_s2)); } else { streamLoc0 = defURI; } System.err.println("url_s "+urls_s[0]); System.err.println("file_s 1: "+file_s1+", 2: "+file_s2); System.err.println("stream0 "+streamLoc0); System.err.println("vid "+vid+", aid "+aid); System.err.println("textureCount "+textureCount); System.err.println("forceES2 "+forceES2); System.err.println("forceES3 "+forceES3); System.err.println("forceGL3 "+forceGL3); System.err.println("forceGLDef "+forceGLDef); System.err.println("swapInterval "+swapInterval); final GLProfile glp; if(forceGLDef) { glp = GLProfile.getDefault(); } else if(forceGL3) { glp = GLProfile.get(GLProfile.GL3); } else if(forceES3) { glp = GLProfile.get(GLProfile.GLES3); } else if(forceES2) { glp = GLProfile.get(GLProfile.GLES2); } else { glp = GLProfile.getGL2ES2(); } System.err.println("GLProfile: "+glp); final GLCapabilities caps = new GLCapabilities(glp); // caps.setAlphaBits(4); // NOTE_ALPHA_BLENDING: We go w/o alpha and blending! final MovieSimple[] mss = new MovieSimple[windowCount]; final GLWindow[] windows = new GLWindow[windowCount]; for(int i=0; i<windowCount; i++) { final Animator anim = new Animator(); anim.start(); windows[i] = GLWindow.create(caps); windows[i].addWindowListener(new WindowAdapter() { public void windowDestroyed(final WindowEvent e) { anim.stop(); } }); mss[i] = new MovieSimple(null); mss[i].setSwapInterval(swapInterval); mss[i].setScaleOrig(!zoom); mss[i].setOrthoProjection(ortho); mss[i].mPlayer.attachObject(WINDOW_KEY, windows[i]); mss[i].mPlayer.addEventListener(myGLMediaEventListener); windows[i].setTitle("Player "+i); windows[i].setSize(width, height); windows[i].setVisible(true); anim.add(windows[i]); final Uri streamLocN; if( 0 == i ) { streamLocN = streamLoc0; } else { if( null != urls_s[i] ) { streamLocN = Uri.cast(urls_s[i]); } else { streamLocN = defURI; } } System.err.println("Win #"+i+": stream "+streamLocN); mss[i].initStream(streamLocN, vid, aid, textureCount); } } }