Rename some methods, have controllers implement onSurfaceDestroyed, revisit controller lifecycle

v2
Mattia Iavarone 6 years ago
parent 7df47cdf45
commit 470ad7783f
  1. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraPreviewTest.java
  2. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java
  3. 118
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  4. 20
      cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
  5. 65
      cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java
  6. 7
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java
  7. 6
      cameraview/src/main/views/com/otaliastudios/cameraview/TextureCameraPreview.java

@ -79,7 +79,7 @@ public abstract class CameraPreviewTest extends BaseTest {
@Test @Test
public void testDefaults() { public void testDefaults() {
ensureAvailable(); ensureAvailable();
assertTrue(preview.isReady()); assertTrue(preview.hasSurface());
assertNotNull(preview.getView()); assertNotNull(preview.getView());
assertNotNull(preview.getOutputClass()); assertNotNull(preview.getOutputClass());
} }
@ -128,7 +128,7 @@ public abstract class CameraPreviewTest extends BaseTest {
// Since desired is 'desired', let's fake a new view size that is consistent with it. // Since desired is 'desired', let's fake a new view size that is consistent with it.
// Ensure crop is not happening anymore. // Ensure crop is not happening anymore.
preview.mCropTask.listen(); preview.mCropTask.listen();
preview.onSurfaceSizeChanged((int) (50f * desired), 50); // Wait... preview.dispatchOnOutputSurfaceSizeChanged((int) (50f * desired), 50); // Wait...
preview.mCropTask.await(); preview.mCropTask.await();
assertEquals(desired, getViewAspectRatioWithScale(), 0.01f); assertEquals(desired, getViewAspectRatioWithScale(), 0.01f);
assertFalse(preview.isCropping()); assertFalse(preview.isCropping());

@ -22,7 +22,7 @@ public class TextureCameraPreviewTest extends CameraPreviewTest {
if (isHardwareAccelerated()) { if (isHardwareAccelerated()) {
super.ensureAvailable(); super.ensureAvailable();
} else { } else {
preview.onSurfaceAvailable( preview.dispatchOnOutputSurfaceAvailable(
surfaceSize.getWidth(), surfaceSize.getWidth(),
surfaceSize.getHeight()); surfaceSize.getHeight());
} }
@ -33,7 +33,7 @@ public class TextureCameraPreviewTest extends CameraPreviewTest {
super.ensureDestroyed(); super.ensureDestroyed();
if (!isHardwareAccelerated()) { if (!isHardwareAccelerated()) {
// Ensure it is called. // Ensure it is called.
preview.onSurfaceDestroyed(); preview.dispatchOnOutputSurfaceDestroyed();
} }
} }

@ -12,7 +12,6 @@ import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import android.util.Log;
import android.support.media.ExifInterface; import android.support.media.ExifInterface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -77,6 +76,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
public void run() { public void run() {
LOG.i("onSurfaceAvailable:", "Inside handler. About to bind."); LOG.i("onSurfaceAvailable:", "Inside handler. About to bind.");
if (shouldBindToSurface()) bindToSurface(); if (shouldBindToSurface()) bindToSurface();
if (shouldStartPreview()) startPreview("onSurfaceAvailable");
} }
}); });
} }
@ -99,13 +99,25 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
LOG.i("onSurfaceChanged:", "Computed a new preview size. Going on."); LOG.i("onSurfaceChanged:", "Computed a new preview size. Going on.");
mPreviewSize = newSize; mPreviewSize = newSize;
mCamera.stopPreview(); mCamera.stopPreview();
applySizesAndStartPreview("onSurfaceChanged:"); startPreview("onSurfaceChanged:");
}
});
}
@Override
public void onSurfaceDestroyed() {
LOG.i("onSurfaceDestroyed");
schedule(null, true, new Runnable() {
@Override
public void run() {
stopPreview();
if (mIsBound) unbindFromSurface();
} }
}); });
} }
private boolean shouldBindToSurface() { private boolean shouldBindToSurface() {
return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsBound; return isCameraAvailable() && mPreview != null && mPreview.hasSurface() && !mIsBound;
} }
// The act of binding an "open" camera to a "ready" preview. // The act of binding an "open" camera to a "ready" preview.
@ -121,18 +133,37 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mCamera.setPreviewTexture((SurfaceTexture) output); mCamera.setPreviewTexture((SurfaceTexture) output);
} }
} catch (IOException e) { } catch (IOException e) {
Log.e("bindToSurface:", "Failed to bind.", e); LOG.e("bindToSurface:", "Failed to bind.", e);
throw new CameraException(e, CameraException.REASON_FAILED_TO_START_PREVIEW); throw new CameraException(e, CameraException.REASON_FAILED_TO_START_PREVIEW);
} }
mCaptureSize = computeCaptureSize(); mCaptureSize = computeCaptureSize();
mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes())); mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes()));
applySizesAndStartPreview("bindToSurface:");
mIsBound = true; mIsBound = true;
} }
@WorkerThread
private void unbindFromSurface() {
mIsBound = false;
mPreviewSize = null;
mCaptureSize = null;
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(null);
} else {
mCamera.setPreviewTexture(null);
}
} catch (IOException e) {
LOG.e("unbindFromSurface", "Could not release surface", e);
}
}
private boolean shouldStartPreview() {
return isCameraAvailable() && mIsBound;
}
// To be called when the preview size is setup or changed. // To be called when the preview size is setup or changed.
private void applySizesAndStartPreview(String log) { private void startPreview(String log) {
LOG.i(log, "Dispatching onCameraPreviewSizeChanged."); LOG.i(log, "Dispatching onCameraPreviewSizeChanged.");
mCameraCallbacks.onCameraPreviewSizeChanged(); mCameraCallbacks.onCameraPreviewSizeChanged();
@ -159,24 +190,28 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
LOG.i(log, "Started preview."); LOG.i(log, "Started preview.");
} }
@WorkerThread private void stopPreview() {
@Override mPreviewFormat = 0;
void onStart() { mFrameManager.release();
if (isCameraAvailable()) { mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
LOG.w("onStart:", "Camera not available. Should not happen."); try {
onStop(); // Should not happen. mCamera.stopPreview();
} catch (Exception e) {
LOG.e("stopPreview", "Could not stop preview", e);
} }
if (collectCameraId()) { }
private void createCamera() {
try { try {
mCamera = Camera.open(mCameraId); mCamera = Camera.open(mCameraId);
} catch (Exception e) { } catch (Exception e) {
LOG.e("onStart:", "Failed to connect. Maybe in use by another app?"); LOG.e("createCamera:", "Failed to connect. Maybe in use by another app?");
throw new CameraException(e, CameraException.REASON_FAILED_TO_CONNECT); throw new CameraException(e, CameraException.REASON_FAILED_TO_CONNECT);
} }
mCamera.setErrorCallback(this); mCamera.setErrorCallback(this);
// Set parameters that might have been set before the camera was opened. // Set parameters that might have been set before the camera was opened.
LOG.i("onStart:", "Applying default parameters."); LOG.i("createCamera:", "Applying default parameters.");
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW)); mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW));
applyDefaultFocus(params); applyDefaultFocus(params);
@ -187,10 +222,32 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mergePlaySound(mPlaySounds); mergePlaySound(mPlaySounds);
params.setRecordingHint(mMode == Mode.VIDEO); params.setRecordingHint(mMode == Mode.VIDEO);
mCamera.setParameters(params); mCamera.setParameters(params);
// Try starting preview.
mCamera.setDisplayOrientation(offset(REF_SENSOR, REF_VIEW)); // <- not allowed during preview mCamera.setDisplayOrientation(offset(REF_SENSOR, REF_VIEW)); // <- not allowed during preview
}
private void destroyCamera() {
try {
LOG.i("destroyCamera:", "Clean up.", "Releasing camera.");
mCamera.release();
LOG.i("destroyCamera:", "Clean up.", "Released camera.");
} catch (Exception e) {
LOG.w("destroyCamera:", "Clean up.", "Exception while releasing camera.", e);
}
mCamera = null;
mCameraOptions = null;
}
@WorkerThread
@Override
void onStart() {
if (isCameraAvailable()) {
LOG.w("onStart:", "Camera not available. Should not happen.");
onStop(); // Should not happen.
}
if (collectCameraId()) {
createCamera();
if (shouldBindToSurface()) bindToSurface(); if (shouldBindToSurface()) bindToSurface();
if (shouldStartPreview()) startPreview("onStart");
LOG.i("onStart:", "Ended"); LOG.i("onStart:", "Ended");
} }
} }
@ -200,31 +257,14 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
void onStop() { void onStop() {
LOG.i("onStop:", "About to clean up."); LOG.i("onStop:", "About to clean up.");
mHandler.get().removeCallbacks(mPostFocusResetRunnable); mHandler.get().removeCallbacks(mPostFocusResetRunnable);
mFrameManager.release();
if (mCamera != null) {
LOG.i("onStop:", "Clean up.", "Ending video. mVideoRecorder is null?", mVideoRecorder == null);
if (mVideoRecorder != null) { if (mVideoRecorder != null) {
mVideoRecorder.stop(); mVideoRecorder.stop();
mVideoRecorder = null; mVideoRecorder = null;
} }
if (mCamera != null) {
try { stopPreview();
LOG.i("onStop:", "Clean up.", "Stopping preview."); unbindFromSurface();
mCamera.setPreviewCallbackWithBuffer(null); destroyCamera();
mCamera.stopPreview();
LOG.i("onStop:", "Clean up.", "Stopped preview.");
} catch (Exception e) {
LOG.w("onStop:", "Clean up.", "Exception while stopping preview.", e);
}
try {
LOG.i("onStop:", "Clean up.", "Releasing camera.");
mCamera.release();
LOG.i("onStop:", "Clean up.", "Released camera.");
} catch (Exception e) {
LOG.w("onStop:", "Clean up.", "Exception while releasing camera.", e);
}
} }
mCameraOptions = null; mCameraOptions = null;
mCamera = null; mCamera = null;
@ -767,7 +807,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
void startAutoFocus(@Nullable final Gesture gesture, final PointF point) { void startAutoFocus(@Nullable final Gesture gesture, final PointF point) {
// Must get width and height from the UI thread. // Must get width and height from the UI thread.
int viewWidth = 0, viewHeight = 0; int viewWidth = 0, viewHeight = 0;
if (mPreview != null && mPreview.isReady()) { if (mPreview != null && mPreview.hasSurface()) {
viewWidth = mPreview.getView().getWidth(); viewWidth = mPreview.getView().getWidth();
viewHeight = mPreview.getView().getHeight(); viewHeight = mPreview.getView().getHeight();
} }

@ -17,6 +17,7 @@ abstract class CameraPreview<T extends View, Output> {
interface SurfaceCallback { interface SurfaceCallback {
void onSurfaceAvailable(); void onSurfaceAvailable();
void onSurfaceChanged(); void onSurfaceChanged();
void onSurfaceDestroyed();
} }
private SurfaceCallback mSurfaceCallback; private SurfaceCallback mSurfaceCallback;
@ -55,8 +56,10 @@ abstract class CameraPreview<T extends View, Output> {
LOG.i("setInputStreamSize:", "desiredW=", width, "desiredH=", height); LOG.i("setInputStreamSize:", "desiredW=", width, "desiredH=", height);
mInputStreamWidth = width; mInputStreamWidth = width;
mInputStreamHeight = height; mInputStreamHeight = height;
if (mInputStreamWidth > 0 && mInputStreamHeight > 0) {
crop(); crop();
} }
}
final Size getInputStreamSize() { final Size getInputStreamSize() {
return new Size(mInputStreamWidth, mInputStreamHeight); return new Size(mInputStreamWidth, mInputStreamHeight);
@ -75,30 +78,35 @@ abstract class CameraPreview<T extends View, Output> {
} }
protected final void onSurfaceAvailable(int width, int height) { protected final void dispatchOnOutputSurfaceAvailable(int width, int height) {
LOG.i("onSurfaceAvailable:", "w=", width, "h=", height); LOG.i("dispatchOnOutputSurfaceAvailable:", "w=", width, "h=", height);
mOutputSurfaceWidth = width; mOutputSurfaceWidth = width;
mOutputSurfaceHeight = height; mOutputSurfaceHeight = height;
if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) {
crop(); crop();
}
mSurfaceCallback.onSurfaceAvailable(); mSurfaceCallback.onSurfaceAvailable();
} }
// As far as I can see, these are the view/surface dimensions. // As far as I can see, these are the view/surface dimensions.
// This is called by subclasses. // This is called by subclasses.
protected final void onSurfaceSizeChanged(int width, int height) { protected final void dispatchOnOutputSurfaceSizeChanged(int width, int height) {
LOG.i("onSurfaceSizeChanged:", "w=", width, "h=", height); LOG.i("dispatchOnOutputSurfaceSizeChanged:", "w=", width, "h=", height);
if (width != mOutputSurfaceWidth || height != mOutputSurfaceHeight) { if (width != mOutputSurfaceWidth || height != mOutputSurfaceHeight) {
mOutputSurfaceWidth = width; mOutputSurfaceWidth = width;
mOutputSurfaceHeight = height; mOutputSurfaceHeight = height;
if (width > 0 && height > 0) {
crop(); crop();
}
mSurfaceCallback.onSurfaceChanged(); mSurfaceCallback.onSurfaceChanged();
} }
} }
protected final void onSurfaceDestroyed() { protected final void dispatchOnOutputSurfaceDestroyed() {
mOutputSurfaceWidth = 0; mOutputSurfaceWidth = 0;
mOutputSurfaceHeight = 0; mOutputSurfaceHeight = 0;
mSurfaceCallback.onSurfaceDestroyed();
} }
void onResume() {} void onResume() {}
@ -107,7 +115,7 @@ abstract class CameraPreview<T extends View, Output> {
void onDestroy() {} void onDestroy() {}
final boolean isReady() { final boolean hasSurface() {
return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0; return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0;
} }

@ -69,7 +69,6 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
glView.setEGLContextClientVersion(2); glView.setEGLContextClientVersion(2);
glView.setRenderer(this); glView.setRenderer(this);
glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); glView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
// glView.getHolder().setFixedSize(600, 300);
glView.getHolder().addCallback(new SurfaceHolder.Callback() { glView.getHolder().addCallback(new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {} public void surfaceCreated(SurfaceHolder holder) {}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
@ -78,7 +77,7 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
@Override @Override
public void surfaceDestroyed(SurfaceHolder holder) { public void surfaceDestroyed(SurfaceHolder holder) {
onSurfaceDestroyed(); dispatchOnOutputSurfaceDestroyed();
} }
}); });
return glView; return glView;
@ -99,7 +98,12 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
@Override @Override
void onDestroy() { void onDestroy() {
super.onDestroy(); super.onDestroy();
releaseInputSurfaceTexture();
}
private void releaseInputSurfaceTexture() {
if (mInputSurfaceTexture != null) { if (mInputSurfaceTexture != null) {
mInputSurfaceTexture.setOnFrameAvailableListener(null);
mInputSurfaceTexture.release(); mInputSurfaceTexture.release();
mInputSurfaceTexture = null; mInputSurfaceTexture = null;
} }
@ -109,9 +113,7 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
} }
} }
// Renderer thread private void createInputSurfaceTexture() {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mOutputViewport = new GLViewport(); mOutputViewport = new GLViewport();
mOutputTextureId = mOutputViewport.createTexture(); mOutputTextureId = mOutputViewport.createTexture();
mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId); mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId);
@ -129,18 +131,52 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
// Renderer thread // Renderer thread
@Override @Override
public void onSurfaceChanged(GL10 gl, int width, int height) { public void onSurfaceCreated(GL10 gl, EGLConfig config) {
createInputSurfaceTexture();
}
// Renderer thread
@Override
public void onSurfaceChanged(GL10 gl, final int width, final int height) {
if (!mDispatched) { if (!mDispatched) {
onSurfaceAvailable(width, height); dispatchOnOutputSurfaceAvailable(width, height);
mDispatched = true; mDispatched = true;
} else { } else if (mOutputSurfaceWidth != width || mOutputSurfaceHeight != height) {
onSurfaceSizeChanged(width, height); // It did actually change. (Got some cases where onSurfaceChanged is called with same values). Go on.
// Since surface size changed does not work, we try to destroy and recreate.
// dispatchOnOutputSurfaceSizeChanged(width, height);
// Recreate the texture...
releaseInputSurfaceTexture();
// Tell the controller that the output surface was destroyed..
// And recreated.
dispatchOnOutputSurfaceDestroyed();
getView().post(new Runnable() {
@Override
public void run() {
// getView().setPreserveEGLContextOnPause(true);
getView().onPause();
getView().onResume();
// getView().setPreserveEGLContextOnPause(false);
getView().queueEvent(new Runnable() {
@Override
public void run() {
createInputSurfaceTexture();
dispatchOnOutputSurfaceAvailable(width, height);
}
});
}
});
} }
} }
@Override @Override
public void onDrawFrame(GL10 gl) { public void onDrawFrame(GL10 gl) {
if (mInputSurfaceTexture == null) return;
// Latch the latest frame. If there isn't anything new, // Latch the latest frame. If there isn't anything new,
// we'll just re-use whatever was there before. // we'll just re-use whatever was there before.
mInputSurfaceTexture.updateTexImage(); mInputSurfaceTexture.updateTexImage();
@ -151,10 +187,19 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
// Draw the video frame. // Draw the video frame.
mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
if (isCropping()) {
Matrix.scaleM(mTransformMatrix, 0, mScaleX, mScaleY, 1); Matrix.scaleM(mTransformMatrix, 0, mScaleX, mScaleY, 1);
}
if (mOutputViewport == null) return;
mOutputViewport.drawFrame(mOutputTextureId, mTransformMatrix); mOutputViewport.drawFrame(mOutputTextureId, mTransformMatrix);
} }
@Override
void setInputStreamSize(int width, int height) {
// mInputSurfaceTexture.setDefaultBufferSize(width, height);
super.setInputStreamSize(width, height);
}
@Override @Override
Class<SurfaceTexture> getOutputClass() { Class<SurfaceTexture> getOutputClass() {
return SurfaceTexture.class; return SurfaceTexture.class;
@ -165,7 +210,6 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
return mInputSurfaceTexture; return mInputSurfaceTexture;
} }
@Override @Override
boolean supportsCropping() { boolean supportsCropping() {
return true; return true;
@ -200,6 +244,7 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
// We must increase width. // We must increase width.
scaleX = target.toFloat() / current.toFloat(); scaleX = target.toFloat() / current.toFloat();
} }
mCropping = scaleX > 1.02f || scaleY > 1.02f;
mScaleX = 1F / scaleX; mScaleX = 1F / scaleX;
mScaleY = 1F / scaleY; mScaleY = 1F / scaleY;
getView().requestRender(); getView().requestRender();

@ -3,7 +3,6 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.View; import android.view.View;
@ -46,17 +45,17 @@ class SurfaceCameraPreview extends CameraPreview<View, SurfaceHolder> {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime); LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime);
if (mFirstTime) { if (mFirstTime) {
onSurfaceAvailable(width, height); dispatchOnOutputSurfaceAvailable(width, height);
mFirstTime = false; mFirstTime = false;
} else { } else {
onSurfaceSizeChanged(width, height); dispatchOnOutputSurfaceSizeChanged(width, height);
} }
} }
@Override @Override
public void surfaceDestroyed(SurfaceHolder holder) { public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed"); LOG.i("callback:", "surfaceDestroyed");
onSurfaceDestroyed(); dispatchOnOutputSurfaceDestroyed();
mFirstTime = true; mFirstTime = true;
} }
}); });

@ -25,17 +25,17 @@ class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> {
@Override @Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
onSurfaceAvailable(width, height); dispatchOnOutputSurfaceAvailable(width, height);
} }
@Override @Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
onSurfaceSizeChanged(width, height); dispatchOnOutputSurfaceSizeChanged(width, height);
} }
@Override @Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
onSurfaceDestroyed(); dispatchOnOutputSurfaceDestroyed();
return true; return true;
} }

Loading…
Cancel
Save