|  |  |  | @ -11,7 +11,14 @@ import android.view.SurfaceHolder; | 
			
		
	
		
			
				
					|  |  |  |  | import android.view.View; | 
			
		
	
		
			
				
					|  |  |  |  | import android.view.ViewGroup; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import java.util.Collections; | 
			
		
	
		
			
				
					|  |  |  |  | import java.util.HashSet; | 
			
		
	
		
			
				
					|  |  |  |  | import java.util.Set; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import javax.microedition.khronos.egl.EGL10; | 
			
		
	
		
			
				
					|  |  |  |  | import javax.microedition.khronos.egl.EGLConfig; | 
			
		
	
		
			
				
					|  |  |  |  | import javax.microedition.khronos.egl.EGLDisplay; | 
			
		
	
		
			
				
					|  |  |  |  | import javax.microedition.khronos.egl.EGLSurface; | 
			
		
	
		
			
				
					|  |  |  |  | import javax.microedition.khronos.opengles.GL10; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | /** | 
			
		
	
	
		
			
				
					|  |  |  | @ -19,42 +26,43 @@ import javax.microedition.khronos.opengles.GL10; | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * - in the SurfaceTexture constructor we pass the GL texture handle that we have created. | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * - The SurfaceTexture is linked to the Camera1 object. It will pass down buffers of data with | 
			
		
	
		
			
				
					|  |  |  |  |  *   a specified size (that is, the Camera1 preview size). | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * - When SurfaceTexture.updateTexImage() is called, it will take the latest image from the camera stream | 
			
		
	
		
			
				
					|  |  |  |  |  *   and update it into the GL texture that was passed. | 
			
		
	
		
			
				
					|  |  |  |  |  * - The SurfaceTexture is linked to the Camera1 object. The camera will pass down buffers of data with | 
			
		
	
		
			
				
					|  |  |  |  |  *   a specified size (that is, the Camera1 preview size). For this reason we don't have to specify | 
			
		
	
		
			
				
					|  |  |  |  |  *   surfaceTexture.setDefaultBufferSize() (like we do, for example, in SnapshotPictureRecorder). | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * - Now we have a GL texture referencing data. It must be drawn. | 
			
		
	
		
			
				
					|  |  |  |  |  *  [Note: it must be drawn using a transformation matrix taken from SurfaceTexture] | 
			
		
	
		
			
				
					|  |  |  |  |  * - When SurfaceTexture.updateTexImage() is called, it will fetch the latest texture image from the | 
			
		
	
		
			
				
					|  |  |  |  |  *   camera stream and assign it to the GL texture that was passed. | 
			
		
	
		
			
				
					|  |  |  |  |  *   Now the GL texture must be drawn using draw* APIs. The SurfaceTexture will also give us | 
			
		
	
		
			
				
					|  |  |  |  |  *   the transformation matrix to be applied. | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * - The easy way to render an OpenGL texture is using the {@link GLSurfaceView} class. | 
			
		
	
		
			
				
					|  |  |  |  |  *   It manages the gl context, hosts a surface and runs a separated rendering thread that will perform | 
			
		
	
		
			
				
					|  |  |  |  |  *   It manages the GL context, hosts a surface and runs a separated rendering thread that will perform | 
			
		
	
		
			
				
					|  |  |  |  |  *   the rendering. | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * - As per docs, we ask the GLSurfaceView to delegate rendering to us, using | 
			
		
	
		
			
				
					|  |  |  |  |  *   {@link GLSurfaceView#setRenderer(GLSurfaceView.Renderer)}. We request a render on the SurfaceView | 
			
		
	
		
			
				
					|  |  |  |  |  *   anytime the SurfaceTexture notifies that it has new data available (see OnFrameAvailableListener below). | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * - Everything is linked: | 
			
		
	
		
			
				
					|  |  |  |  |  * - So in short: | 
			
		
	
		
			
				
					|  |  |  |  |  *   - The SurfaceTexture has buffers of data of mInputStreamSize | 
			
		
	
		
			
				
					|  |  |  |  |  *   - The SurfaceView hosts a view (and surface) of size mOutputSurfaceSize | 
			
		
	
		
			
				
					|  |  |  |  |  *   - The SurfaceView hosts a view (and a surface) of size mOutputSurfaceSize. | 
			
		
	
		
			
				
					|  |  |  |  |  *     These are determined by the CameraView.onMeasure method. | 
			
		
	
		
			
				
					|  |  |  |  |  *   - We have a GL rich texture to be drawn (in the given method & thread). | 
			
		
	
		
			
				
					|  |  |  |  |  * | 
			
		
	
		
			
				
					|  |  |  |  |  * TODO | 
			
		
	
		
			
				
					|  |  |  |  |  * CROPPING: Managed to do this using Matrix transformation. | 
			
		
	
		
			
				
					|  |  |  |  |  * UPDATING: Managed to work using view.onPause and onResume. | 
			
		
	
		
			
				
					|  |  |  |  |  * TAKING PICTURES: Sometime the snapshot takes ages... Can't reproduce anymore. Cool. | 
			
		
	
		
			
				
					|  |  |  |  |  * TAKING VIDEOS: Still have not tried... | 
			
		
	
		
			
				
					|  |  |  |  |  * This class will provide rendering callbacks to anyone who registers a {@link RendererFrameCallback}. | 
			
		
	
		
			
				
					|  |  |  |  |  * Callbacks are guaranteed to be called on the renderer thread, which means that we can fetch | 
			
		
	
		
			
				
					|  |  |  |  |  * the GL context that was created and is managed by the {@link GLSurfaceView}. | 
			
		
	
		
			
				
					|  |  |  |  |  */ | 
			
		
	
		
			
				
					|  |  |  |  | class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> implements GLSurfaceView.Renderer { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private boolean mDispatched; | 
			
		
	
		
			
				
					|  |  |  |  |     private final float[] mTransformMatrix = new float[16]; | 
			
		
	
		
			
				
					|  |  |  |  |     private int mOutputTextureId = -1; | 
			
		
	
		
			
				
					|  |  |  |  |     private int mOutputTextureId = 0; | 
			
		
	
		
			
				
					|  |  |  |  |     private SurfaceTexture mInputSurfaceTexture; | 
			
		
	
		
			
				
					|  |  |  |  |     private EglViewport mOutputViewport; | 
			
		
	
		
			
				
					|  |  |  |  |     private RendererFrameCallback mRendererFrameCallback; | 
			
		
	
		
			
				
					|  |  |  |  |     private Set<RendererFrameCallback> mRendererFrameCallbacks = Collections.synchronizedSet(new HashSet<RendererFrameCallback>()); | 
			
		
	
		
			
				
					|  |  |  |  |     /* for tests */ float mScaleX = 1F; | 
			
		
	
		
			
				
					|  |  |  |  |     /* for tests */ float mScaleY = 1F; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     GLCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) { | 
			
		
	
		
			
				
					|  |  |  |  |         super(context, parent, callback); | 
			
		
	
	
		
			
				
					|  |  |  | @ -97,10 +105,8 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     void onDestroy() { | 
			
		
	
		
			
				
					|  |  |  |  |         super.onDestroy(); | 
			
		
	
		
			
				
					|  |  |  |  |         releaseInputSurfaceTexture(); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void releaseInputSurfaceTexture() { | 
			
		
	
		
			
				
					|  |  |  |  |         // View is gone, so EGL context is gone: callbacks make no sense anymore.
 | 
			
		
	
		
			
				
					|  |  |  |  |         mRendererFrameCallbacks.clear(); | 
			
		
	
		
			
				
					|  |  |  |  |         if (mInputSurfaceTexture != null) { | 
			
		
	
		
			
				
					|  |  |  |  |             mInputSurfaceTexture.setOnFrameAvailableListener(null); | 
			
		
	
		
			
				
					|  |  |  |  |             mInputSurfaceTexture.release(); | 
			
		
	
	
		
			
				
					|  |  |  | @ -113,28 +119,31 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void createInputSurfaceTexture() { | 
			
		
	
		
			
				
					|  |  |  |  |     // Renderer thread
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceCreated(GL10 gl, EGLConfig config) { | 
			
		
	
		
			
				
					|  |  |  |  |         mOutputViewport = new EglViewport(); | 
			
		
	
		
			
				
					|  |  |  |  |         mOutputTextureId = mOutputViewport.createTexture(); | 
			
		
	
		
			
				
					|  |  |  |  |         mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().queueEvent(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 for (RendererFrameCallback callback : mRendererFrameCallbacks) { | 
			
		
	
		
			
				
					|  |  |  |  |                     callback.onRendererTextureCreated(mOutputTextureId); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // Since we are using GLSurfaceView.RENDERMODE_WHEN_DIRTY, we must notify the SurfaceView
 | 
			
		
	
		
			
				
					|  |  |  |  |         // of dirtyness, so that it draws again. This is how it's done.
 | 
			
		
	
		
			
				
					|  |  |  |  |         mInputSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void onFrameAvailable(SurfaceTexture surfaceTexture) { | 
			
		
	
		
			
				
					|  |  |  |  |                 // requestRender is thread-safe.
 | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().requestRender(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().requestRender(); // requestRender is thread-safe.
 | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // Renderer thread
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceCreated(GL10 gl, EGLConfig config) { | 
			
		
	
		
			
				
					|  |  |  |  |         createInputSurfaceTexture(); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // Renderer thread
 | 
			
		
	
		
			
				
					|  |  |  |  |     @SuppressWarnings("StatementWithEmptyBody") | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
	
		
			
				
					|  |  |  | @ -143,27 +152,27 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple | 
			
		
	
		
			
				
					|  |  |  |  |             dispatchOnOutputSurfaceAvailable(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |             mDispatched = true; | 
			
		
	
		
			
				
					|  |  |  |  |         } else if (mOutputSurfaceWidth == width && mOutputSurfaceHeight == height) { | 
			
		
	
		
			
				
					|  |  |  |  |                 // This change can be triggered by ourselves (see below). Ignore.
 | 
			
		
	
		
			
				
					|  |  |  |  |             // I was experimenting and this was happening.
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Not sure if it is stil needed now.
 | 
			
		
	
		
			
				
					|  |  |  |  |         } else { | 
			
		
	
		
			
				
					|  |  |  |  |             // With other CameraPreview implementation we could just dispatch the 'size changed' event
 | 
			
		
	
		
			
				
					|  |  |  |  |             // to the controller and everything would go straight. In case of GL, apparently we have to:
 | 
			
		
	
		
			
				
					|  |  |  |  |             // - create a new texture (release the old)
 | 
			
		
	
		
			
				
					|  |  |  |  |             // - unbind camera and surface
 | 
			
		
	
		
			
				
					|  |  |  |  |             // - stop camera preview
 | 
			
		
	
		
			
				
					|  |  |  |  |             // - recreate the GL context using view.onPause() and onResume()
 | 
			
		
	
		
			
				
					|  |  |  |  |             // ...
 | 
			
		
	
		
			
				
					|  |  |  |  |             onSizeChangeImplementation4(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |             // to the controller and everything would go straight. In case of GL, apparently we have to
 | 
			
		
	
		
			
				
					|  |  |  |  |             // force recreate the EGLContext by calling onPause and onResume in the UI thread.
 | 
			
		
	
		
			
				
					|  |  |  |  |             dispatchOnOutputSurfaceDestroyed(); | 
			
		
	
		
			
				
					|  |  |  |  |             getView().post(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |                 @Override | 
			
		
	
		
			
				
					|  |  |  |  |                 public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                     getView().onPause(); | 
			
		
	
		
			
				
					|  |  |  |  |                     getView().onResume(); | 
			
		
	
		
			
				
					|  |  |  |  |                     dispatchOnOutputSurfaceAvailable(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             }); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // Renderer thread
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onDrawFrame(GL10 gl) { | 
			
		
	
		
			
				
					|  |  |  |  |         // This are only needed with some implementations,
 | 
			
		
	
		
			
				
					|  |  |  |  |         // and implementation4 seems to work well without them.
 | 
			
		
	
		
			
				
					|  |  |  |  |         // if (mInputSurfaceTexture == null) return;
 | 
			
		
	
		
			
				
					|  |  |  |  |         // if (mOutputViewport == null) return;
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // Latch the latest frame.  If there isn't anything new,
 | 
			
		
	
		
			
				
					|  |  |  |  |         // we'll just re-use whatever was there before.
 | 
			
		
	
		
			
				
					|  |  |  |  |         mInputSurfaceTexture.updateTexImage(); | 
			
		
	
	
		
			
				
					|  |  |  | @ -172,24 +181,21 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple | 
			
		
	
		
			
				
					|  |  |  |  |             return; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         if (mRendererFrameCallback != null) { | 
			
		
	
		
			
				
					|  |  |  |  |             mRendererFrameCallback.onRendererFrame(mInputSurfaceTexture, mScaleX, mScaleY); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // Draw the video frame.
 | 
			
		
	
		
			
				
					|  |  |  |  |         mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); | 
			
		
	
		
			
				
					|  |  |  |  |         if (isCropping()) { | 
			
		
	
		
			
				
					|  |  |  |  |             // Scaling is easy. However:
 | 
			
		
	
		
			
				
					|  |  |  |  |             // If the view is 10x1000 (very tall), it will show only the left strip of the preview (not the center one).
 | 
			
		
	
		
			
				
					|  |  |  |  |             // If the view is 1000x10 (very large), it will show only the bottom strip of the preview (not the center one).
 | 
			
		
	
		
			
				
					|  |  |  |  |             // We must use Matrix.translateM, and it must happen before the crop.
 | 
			
		
	
		
			
				
					|  |  |  |  |             // So we must use Matrix.translateM, and it must happen before the crop.
 | 
			
		
	
		
			
				
					|  |  |  |  |             float translX = (1F - mScaleX) / 2F; | 
			
		
	
		
			
				
					|  |  |  |  |             float translY = (1F - mScaleY) / 2F; | 
			
		
	
		
			
				
					|  |  |  |  |             Matrix.translateM(mTransformMatrix, 0, translX, translY, 0); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Crop. Works, but without translation, it is not centered.
 | 
			
		
	
		
			
				
					|  |  |  |  |             Matrix.scaleM(mTransformMatrix, 0, mScaleX, mScaleY, 1); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         mOutputViewport.drawFrame(mOutputTextureId, mTransformMatrix); | 
			
		
	
		
			
				
					|  |  |  |  |         for (RendererFrameCallback callback : mRendererFrameCallbacks) { | 
			
		
	
		
			
				
					|  |  |  |  |             callback.onRendererFrame(mInputSurfaceTexture, mScaleX, mScaleY); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
	
		
			
				
					|  |  |  | @ -207,9 +213,6 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple | 
			
		
	
		
			
				
					|  |  |  |  |         return true; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /* for tests */ float mScaleX = 1F; | 
			
		
	
		
			
				
					|  |  |  |  |     /* for tests */ float mScaleY = 1F; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * To crop in GL, we could actually use view.setScaleX and setScaleY, but only from Android N onward. | 
			
		
	
		
			
				
					|  |  |  |  |      * See documentation: https://developer.android.com/reference/android/view/SurfaceView
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -244,114 +247,39 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple | 
			
		
	
		
			
				
					|  |  |  |  |         mCropTask.end(null); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     interface RendererFrameCallback { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // This does work but looks like a lot of stuff.
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void onSizeChangeImplementation1(final int width, final int height) { | 
			
		
	
		
			
				
					|  |  |  |  |         releaseInputSurfaceTexture(); | 
			
		
	
		
			
				
					|  |  |  |  |         dispatchOnOutputSurfaceDestroyed(); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().post(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onPause(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onResume(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().queueEvent(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |                     @Override | 
			
		
	
		
			
				
					|  |  |  |  |                     public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                         createInputSurfaceTexture(); | 
			
		
	
		
			
				
					|  |  |  |  |                         dispatchOnOutputSurfaceAvailable(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |                     } | 
			
		
	
		
			
				
					|  |  |  |  |                 }); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // This does not work. We get: startPreview failed.
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void onSizeChangeImplementation2(final int width, final int height) { | 
			
		
	
		
			
				
					|  |  |  |  |         releaseInputSurfaceTexture(); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().post(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onPause(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onResume(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().queueEvent(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |                     @Override | 
			
		
	
		
			
				
					|  |  |  |  |                     public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                         createInputSurfaceTexture(); | 
			
		
	
		
			
				
					|  |  |  |  |                         dispatchOnOutputSurfaceSizeChanged(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |                     } | 
			
		
	
		
			
				
					|  |  |  |  |                 }); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // Works! So we don't need to recreate the GL texture.
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void onSizeChangeImplementation3(final int width, final int height) { | 
			
		
	
		
			
				
					|  |  |  |  |         dispatchOnOutputSurfaceDestroyed(); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().post(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onPause(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onResume(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().queueEvent(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |                     @Override | 
			
		
	
		
			
				
					|  |  |  |  |                     public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                         dispatchOnOutputSurfaceAvailable(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |                     } | 
			
		
	
		
			
				
					|  |  |  |  |                 }); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // Works! This is getting easy.
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void onSizeChangeImplementation4(final int width, final int height) { | 
			
		
	
		
			
				
					|  |  |  |  |         dispatchOnOutputSurfaceDestroyed(); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().post(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onPause(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onResume(); | 
			
		
	
		
			
				
					|  |  |  |  |                 dispatchOnOutputSurfaceAvailable(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |         /** | 
			
		
	
		
			
				
					|  |  |  |  |          * Called on the renderer thread, hopefully only once, to notify that | 
			
		
	
		
			
				
					|  |  |  |  |          * the texture was created (or to inform a new callback of the old texture). | 
			
		
	
		
			
				
					|  |  |  |  |          * | 
			
		
	
		
			
				
					|  |  |  |  |          * @param textureId the GL texture linked to the image stream | 
			
		
	
		
			
				
					|  |  |  |  |          */ | 
			
		
	
		
			
				
					|  |  |  |  |         void onRendererTextureCreated(int textureId); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // Does not work. onPause and onResume must be called on the UI thread.
 | 
			
		
	
		
			
				
					|  |  |  |  |     // This make sense.
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void onSizeChangeImplementation5(final int width, final int height) { | 
			
		
	
		
			
				
					|  |  |  |  |         dispatchOnOutputSurfaceDestroyed(); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().onPause(); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().onResume(); | 
			
		
	
		
			
				
					|  |  |  |  |         dispatchOnOutputSurfaceAvailable(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |         /** | 
			
		
	
		
			
				
					|  |  |  |  |          * Called on the renderer thread after each frame was drawn. | 
			
		
	
		
			
				
					|  |  |  |  |          * You are not supposed to hold for too long onto this thread, because | 
			
		
	
		
			
				
					|  |  |  |  |          * well, it is the rendering thread. | 
			
		
	
		
			
				
					|  |  |  |  |          * | 
			
		
	
		
			
				
					|  |  |  |  |          * @param surfaceTexture the texture to get transformation | 
			
		
	
		
			
				
					|  |  |  |  |          * @param scaleX the scaleX (in REF_VIEW) value | 
			
		
	
		
			
				
					|  |  |  |  |          * @param scaleY the scaleY (in REF_VIEW) value | 
			
		
	
		
			
				
					|  |  |  |  |          */ | 
			
		
	
		
			
				
					|  |  |  |  |         void onRendererFrame(SurfaceTexture surfaceTexture, float scaleX, float scaleY); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // Does NOT work. The EGL context must be recreated
 | 
			
		
	
		
			
				
					|  |  |  |  |     // for this to work out.
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void onSizeChangeImplementation6(final int width, final int height) { | 
			
		
	
		
			
				
					|  |  |  |  |         dispatchOnOutputSurfaceDestroyed(); | 
			
		
	
		
			
				
					|  |  |  |  |         getView().post(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |     void addRendererFrameCallback(@NonNull final RendererFrameCallback callback) { | 
			
		
	
		
			
				
					|  |  |  |  |         getView().queueEvent(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().setPreserveEGLContextOnPause(true); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onPause(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().onResume(); | 
			
		
	
		
			
				
					|  |  |  |  |                 getView().setPreserveEGLContextOnPause(false); | 
			
		
	
		
			
				
					|  |  |  |  |                 dispatchOnOutputSurfaceAvailable(width, height); | 
			
		
	
		
			
				
					|  |  |  |  |                 mRendererFrameCallbacks.add(callback); | 
			
		
	
		
			
				
					|  |  |  |  |                 if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     interface RendererFrameCallback { | 
			
		
	
		
			
				
					|  |  |  |  |         // Renderer thread.
 | 
			
		
	
		
			
				
					|  |  |  |  |         void onRendererTextureCreated(int textureId); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // Renderer thread.
 | 
			
		
	
		
			
				
					|  |  |  |  |         void onRendererFrame(SurfaceTexture surfaceTexture, float scaleX, float scaleY); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     void setRendererFrameCallback(@Nullable RendererFrameCallback callback) { | 
			
		
	
		
			
				
					|  |  |  |  |         mRendererFrameCallback = callback; | 
			
		
	
		
			
				
					|  |  |  |  |         if (mRendererFrameCallback != null && mOutputTextureId != 0) { | 
			
		
	
		
			
				
					|  |  |  |  |             mRendererFrameCallback.onRendererTextureCreated(mOutputTextureId); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     void removeRendererFrameCallback(@NonNull final RendererFrameCallback callback) { | 
			
		
	
		
			
				
					|  |  |  |  |         mRendererFrameCallbacks.remove(callback); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
	
		
			
				
					|  |  |  | 
 |