feat(android): Android ui thread (#2560)

* Move heavy work to a dedicated background thread. Improves camera initial loading and resumes from background.

Details:

- Use a HandlerThread to delegate heavy tasks to background. The thread is managed by the view, and passed down to the implementation in case it also needs to use it. The view will fire start calls and other possibly heavy operations in this thread to avoid ANRs. Some code sent to this thread:
    - start calls: start is extremely heavy and will cause ANRs on some devices, especially when coming back from background
    - Camera1: some preset changes fire a stop/start sequence. These will now happen in the background thread
    - take picture and start recording (from view class) will also start in this thread

- Add some extra null checks

- View was not properly cleaning up itself on destroy (host destroy event was never fired)

* catch possible errors when starting camera preview. This might still randomly fail on some devices for some reason.

* delay capture in progress until we have resumed/paused preview.

* do not crash the app if set texture setup failed

* more synchronized checks to prevent crashes due to concurrent camera updates

* remove unused imports
This commit is contained in:
cristianoccazinsp 2019-11-04 14:53:42 -03:00 committed by Sibelius Seraphini
parent 0158ebfd39
commit 87774dd370
8 changed files with 399 additions and 259 deletions

View File

@ -94,6 +94,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
private final SizeMap mPreviewSizes = new SizeMap();
private boolean mIsPreviewActive = false;
private boolean mShowingPreview = true; // preview enabled by default
private final SizeMap mPictureSizes = new SizeMap();
@ -101,8 +102,6 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
private AspectRatio mAspectRatio;
private boolean mShowingPreview;
private boolean mAutoFocus;
private int mFacing;
@ -127,8 +126,9 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
private SurfaceTexture mPreviewTexture;
Camera1(Callback callback, PreviewImpl preview) {
super(callback, preview);
Camera1(Callback callback, PreviewImpl preview, Handler bgHandler) {
super(callback, preview, bgHandler);
preview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged() {
@ -137,7 +137,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
@Override
public void onSurfaceDestroyed() {
stop();
stop();
}
});
}
@ -150,12 +150,24 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
// pause preview calls
// capture callbacks will handle it if needed afterwards.
if(!isPictureCaptureInProgress.get() && !mIsRecording.get()){
synchronized (this) {
mustUpdateSurface = false;
setUpPreview();
mIsPreviewActive = false;
adjustCameraParameters();
}
mBgHandler.post(new Runnable() {
@Override
public void run() {
synchronized(Camera1.this){
// check for camera null again since it might have changed
if(mCamera != null){
mustUpdateSurface = false;
setUpPreview();
adjustCameraParameters();
// only start preview if we are showing it
if(mShowingPreview){
startCameraPreview();
}
}
}
}
});
}
else{
mustUpdateSurface = true;
@ -165,30 +177,37 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
@Override
boolean start() {
synchronized (this) {
synchronized(this){
chooseCamera();
if (!openCamera()) {
mCallback.onMountError();
// returning false will result in invoking this method again
return true;
}
// if our preview layer is not ready
// do not set it up. Surface handler will do it for us
// once ready.
// This prevents some redundant camera work
if (mPreview.isReady()) {
setUpPreview();
if(mShowingPreview){
startCameraPreview();
}
}
mShowingPreview = true;
startCameraPreview();
return true;
}
}
@Override
void stop() {
// make sure no other threads are trying to do this at the same time
// such as another call to stop() from surface destroyed
// such as another call to stop from surface destroyed
// or host destroyed. Should avoid crashes with concurrent calls
synchronized (this) {
synchronized(this){
if (mMediaRecorder != null) {
try{
mMediaRecorder.stop();
@ -214,10 +233,10 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
if (mCamera != null) {
mIsPreviewActive = false;
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
}
mShowingPreview = false;
releaseCamera();
}
@ -227,43 +246,68 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
@SuppressLint("NewApi")
void setUpPreview() {
try {
if (mPreviewTexture != null) {
mCamera.setPreviewTexture(mPreviewTexture);
} else if (mPreview.getOutputClass() == SurfaceHolder.class) {
final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
if (needsToStopPreview) {
mCamera.stopPreview();
mIsPreviewActive = false;
if(mCamera != null){
if (mPreviewTexture != null) {
mCamera.setPreviewTexture(mPreviewTexture);
} else if (mPreview.getOutputClass() == SurfaceHolder.class) {
final boolean needsToStopPreview = mIsPreviewActive && Build.VERSION.SDK_INT < 14;
if (needsToStopPreview) {
mCamera.stopPreview();
mIsPreviewActive = false;
}
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
if (needsToStopPreview) {
startCameraPreview();
}
} else {
mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
}
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
if (needsToStopPreview) {
startCameraPreview();
}
} else {
mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
Log.e("CAMERA_1::", "setUpPreview failed", e);
}
}
private void startCameraPreview() {
mCamera.startPreview();
mIsPreviewActive = true;
if (mIsScanning) {
mCamera.setPreviewCallback(this);
// only start the preview if we didn't yet.
if(!mIsPreviewActive && mCamera != null){
try{
mIsPreviewActive = true;
mCamera.startPreview();
if (mIsScanning) {
mCamera.setPreviewCallback(this);
}
}
catch(Exception e){
mIsPreviewActive = false;
Log.e("CAMERA_1::", "startCameraPreview failed", e);
}
}
}
@Override
public void resumePreview() {
startCameraPreview();
mBgHandler.post(new Runnable() {
@Override
public void run() {
synchronized(this){
mShowingPreview = true;
startCameraPreview();
}
}
});
}
@Override
public void pausePreview() {
mCamera.stopPreview();
mIsPreviewActive = false;
synchronized(this){
mIsPreviewActive = false;
mShowingPreview = false;
if(mCamera != null){
mCamera.stopPreview();
}
}
}
@Override
@ -277,10 +321,17 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
return;
}
mFacing = facing;
if (isCameraOpened()) {
stop();
start();
}
mBgHandler.post(new Runnable() {
@Override
public void run() {
if (isCameraOpened()) {
stop();
start();
}
}
});
}
@Override
@ -299,10 +350,15 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
// Passing null will always yield true
if(!Objects.equals(_mCameraId, String.valueOf(mCameraId))){
// this will call chooseCamera
if (isCameraOpened()) {
stop();
start();
}
mBgHandler.post(new Runnable() {
@Override
public void run() {
if (isCameraOpened()) {
stop();
start();
}
}
});
}
}
@ -360,13 +416,16 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
} else {
mPictureSize = size;
}
if (mCameraParameters != null && mCamera != null) {
mCameraParameters.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight());
try{
mCamera.setParameters(mCameraParameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
synchronized(this){
if (mCameraParameters != null && mCamera != null) {
mCameraParameters.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight());
try{
mCamera.setParameters(mCameraParameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
}
}
}
@ -377,7 +436,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
@Override
boolean setAspectRatio(AspectRatio ratio) {
boolean setAspectRatio(final AspectRatio ratio) {
if (mAspectRatio == null || !isCameraOpened()) {
// Handle this later when camera is opened
mAspectRatio = ratio;
@ -388,7 +447,16 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
// do nothing, ratio remains unchanged. Consistent with Camera2 and initial mount behaviour
} else {
mAspectRatio = ratio;
adjustCameraParameters();
mBgHandler.post(new Runnable() {
@Override
public void run() {
synchronized(Camera1.this){
if(mCamera != null){
adjustCameraParameters();
}
}
}
});
return true;
}
}
@ -405,13 +473,17 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
if (mAutoFocus == autoFocus) {
return;
}
if (setAutoFocusInternal(autoFocus)) {
try{
mCamera.setParameters(mCameraParameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
synchronized(this){
if (setAutoFocusInternal(autoFocus)) {
try{
if(mCamera != null){
mCamera.setParameters(mCameraParameters);
}
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
}
}
}
@ -430,12 +502,14 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
return;
}
if (setFlashInternal(flash)) {
try{
mCamera.setParameters(mCameraParameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
try{
if(mCamera != null){
mCamera.setParameters(mCameraParameters);
}
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
}
}
@ -456,13 +530,16 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
return;
}
if (setExposureInternal(exposure)) {
try{
mCamera.setParameters(mCameraParameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
try{
if(mCamera != null){
mCamera.setParameters(mCameraParameters);
}
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
}
}
@Override
@ -481,12 +558,14 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
return;
}
if (setZoomInternal(zoom)) {
try{
mCamera.setParameters(mCameraParameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
try{
if(mCamera != null){
mCamera.setParameters(mCameraParameters);
}
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
}
}
@ -502,12 +581,14 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
return;
}
if (setWhiteBalanceInternal(whiteBalance)) {
try{
mCamera.setParameters(mCameraParameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
try{
if(mCamera != null){
mCamera.setParameters(mCameraParameters);
}
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
}
}
@ -597,7 +678,6 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
isPictureCaptureInProgress.set(false);
// this shouldn't be needed and messes up autoFocusPointOfInterest
// camera.cancelAutoFocus();
@ -614,6 +694,8 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
camera.setPreviewCallback(null);
}
isPictureCaptureInProgress.set(false);
mOrientation = Constants.ORIENTATION_AUTO;
mCallback.onPictureTaken(data, displayOrientationToOrientationEnum(mDeviceOrientation));
@ -687,14 +769,14 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
@Override
void setDisplayOrientation(int displayOrientation) {
synchronized (this) {
void setDisplayOrientation(final int displayOrientation) {
synchronized(this){
if (mDisplayOrientation == displayOrientation) {
return;
}
mDisplayOrientation = displayOrientation;
if (isCameraOpened()) {
final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
boolean needsToStopPreview = mIsPreviewActive && Build.VERSION.SDK_INT < 14;
if (needsToStopPreview) {
mCamera.stopPreview();
mIsPreviewActive = false;
@ -714,8 +796,8 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
@Override
void setDeviceOrientation(int deviceOrientation) {
synchronized (this) {
void setDeviceOrientation(final int deviceOrientation) {
synchronized(this){
if (mDeviceOrientation == deviceOrientation) {
return;
}
@ -733,27 +815,33 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
@Override
public void setPreviewTexture(SurfaceTexture surfaceTexture) {
try {
if (mCamera == null) {
mPreviewTexture = surfaceTexture;
return;
public void setPreviewTexture(final SurfaceTexture surfaceTexture) {
mBgHandler.post(new Runnable() {
@Override
public void run() {
try{
if (mCamera == null) {
mPreviewTexture = surfaceTexture;
return;
}
mCamera.stopPreview();
mIsPreviewActive = false;
if (surfaceTexture == null) {
mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
} else {
mCamera.setPreviewTexture(surfaceTexture);
}
mPreviewTexture = surfaceTexture;
startCameraPreview();
} catch (IOException e) {
Log.e("CAMERA_1::", "setPreviewTexture failed", e);
}
}
mCamera.stopPreview();
mIsPreviewActive = false;
if (surfaceTexture == null) {
mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
} else {
mCamera.setPreviewTexture(surfaceTexture);
}
mPreviewTexture = surfaceTexture;
startCameraPreview();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
@Override
@ -845,7 +933,8 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
// Always re-apply camera parameters
mPictureSize = mPictureSizes.sizes(mAspectRatio).last();
if (mShowingPreview) {
boolean needsToStopPreview = mIsPreviewActive;
if (needsToStopPreview) {
mCamera.stopPreview();
mIsPreviewActive = false;
}
@ -870,7 +959,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
if (mShowingPreview) {
if (needsToStopPreview) {
startCameraPreview();
}
}
@ -916,89 +1005,96 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
}
// Most credit: https://github.com/CameraKit/camerakit-android/blob/master/camerakit-core/src/main/api16/com/wonderkiln/camerakit/Camera1.java
void setFocusArea(float x, float y) {
if (mCamera != null) {
Camera.Parameters parameters = mCamera.getParameters();
if (parameters == null) return;
void setFocusArea(final float x, final float y) {
mBgHandler.post(new Runnable() {
@Override
public void run() {
synchronized(Camera1.this){
if (mCamera != null) {
Camera.Parameters parameters = mCamera.getParameters();
if (parameters == null) return;
String focusMode = parameters.getFocusMode();
Rect rect = calculateFocusArea(x, y);
String focusMode = parameters.getFocusMode();
Rect rect = calculateFocusArea(x, y);
List<Camera.Area> meteringAreas = new ArrayList<>();
meteringAreas.add(new Camera.Area(rect, FOCUS_METERING_AREA_WEIGHT_DEFAULT));
List<Camera.Area> meteringAreas = new ArrayList<>();
meteringAreas.add(new Camera.Area(rect, FOCUS_METERING_AREA_WEIGHT_DEFAULT));
if (parameters.getMaxNumFocusAreas() != 0 && focusMode != null &&
(focusMode.equals(Camera.Parameters.FOCUS_MODE_AUTO) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_MACRO) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(meteringAreas);
if (parameters.getMaxNumMeteringAreas() > 0) {
parameters.setMeteringAreas(meteringAreas);
}
if (!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
return; //cannot autoFocus
}
try{
mCamera.setParameters(parameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
if (parameters.getMaxNumFocusAreas() != 0 && focusMode != null &&
(focusMode.equals(Camera.Parameters.FOCUS_MODE_AUTO) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_MACRO) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(meteringAreas);
if (parameters.getMaxNumMeteringAreas() > 0) {
parameters.setMeteringAreas(meteringAreas);
}
if (!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
return; //cannot autoFocus
}
try{
mCamera.setParameters(parameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
try{
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
//resetFocus(success, camera);
try{
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
//resetFocus(success, camera);
}
});
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "autoFocus failed", e);
}
} else if (parameters.getMaxNumMeteringAreas() > 0) {
if (!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
return; //cannot autoFocus
}
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(meteringAreas);
parameters.setMeteringAreas(meteringAreas);
try{
mCamera.setParameters(parameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
try{
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
//resetFocus(success, camera);
}
});
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "autoFocus failed", e);
}
} else {
try{
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
//mCamera.cancelAutoFocus();
}
});
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "autoFocus failed", e);
}
}
});
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "autoFocus failed", e);
}
} else if (parameters.getMaxNumMeteringAreas() > 0) {
if (!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
return; //cannot autoFocus
}
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(meteringAreas);
parameters.setMeteringAreas(meteringAreas);
try{
mCamera.setParameters(parameters);
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "setParameters failed", e);
}
try{
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
//resetFocus(success, camera);
}
});
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "autoFocus failed", e);
}
} else {
try{
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
//mCamera.cancelAutoFocus();
}
});
}
catch(RuntimeException e ) {
Log.e("CAMERA_1::", "autoFocus failed", e);
}
}
}
}
});
}
private void resetFocus(final boolean success, final Camera camera) {

View File

@ -263,8 +263,8 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
private Rect mInitialCropRegion;
Camera2(Callback callback, PreviewImpl preview, Context context) {
super(callback, preview);
Camera2(Callback callback, PreviewImpl preview, Context context, Handler bgHandler) {
super(callback, preview, bgHandler);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
mCameraManager.registerAvailabilityCallback(new CameraManager.AvailabilityCallback() {
@Override

View File

@ -20,13 +20,14 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Handler;
@TargetApi(23)
class Camera2Api23 extends Camera2 {
Camera2Api23(Callback callback, PreviewImpl preview, Context context) {
super(callback, preview, context);
Camera2Api23(Callback callback, PreviewImpl preview, Context context, Handler bgHandler) {
super(callback, preview, context, bgHandler);
}
@Override

View File

@ -22,6 +22,8 @@ import android.graphics.Rect;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.os.Build;
import android.os.HandlerThread;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.IntDef;
@ -91,6 +93,10 @@ public class CameraView extends FrameLayout {
private final DisplayOrientationDetector mDisplayOrientationDetector;
protected HandlerThread mBgThread;
protected Handler mBgHandler;
public CameraView(Context context, boolean fallbackToOldApi) {
this(context, null, fallbackToOldApi);
}
@ -102,6 +108,13 @@ public class CameraView extends FrameLayout {
@SuppressWarnings("WrongConstant")
public CameraView(Context context, AttributeSet attrs, int defStyleAttr, boolean fallbackToOldApi) {
super(context, attrs, defStyleAttr);
// bg hanadler for non UI heavy work
mBgThread = new HandlerThread("RNCamera-Handler-Thread");
mBgThread.start();
mBgHandler = new Handler(mBgThread.getLooper());
if (isInEditMode()){
mCallbacks = null;
mDisplayOrientationDetector = null;
@ -114,11 +127,11 @@ public class CameraView extends FrameLayout {
final PreviewImpl preview = createPreviewImpl(context);
mCallbacks = new CallbackBridge();
if (fallbackToOldApi || Build.VERSION.SDK_INT < 21) {
mImpl = new Camera1(mCallbacks, preview);
mImpl = new Camera1(mCallbacks, preview, mBgHandler);
} else if (Build.VERSION.SDK_INT < 23) {
mImpl = new Camera2(mCallbacks, preview, context);
mImpl = new Camera2(mCallbacks, preview, context, mBgHandler);
} else {
mImpl = new Camera2Api23(mCallbacks, preview, context);
mImpl = new Camera2Api23(mCallbacks, preview, context, mBgHandler);
}
// Display orientation detector
@ -131,6 +144,13 @@ public class CameraView extends FrameLayout {
};
}
public void cleanup(){
if(mBgThread != null){
mBgThread.quitSafely();
mBgThread = null;
}
}
@NonNull
private PreviewImpl createPreviewImpl(Context context) {
PreviewImpl preview;
@ -269,9 +289,9 @@ public class CameraView extends FrameLayout {
stop();
}
if (Build.VERSION.SDK_INT < 23) {
mImpl = new Camera2(mCallbacks, mImpl.mPreview, mContext);
mImpl = new Camera2(mCallbacks, mImpl.mPreview, mContext, mBgHandler);
} else {
mImpl = new Camera2Api23(mCallbacks, mImpl.mPreview, mContext);
mImpl = new Camera2Api23(mCallbacks, mImpl.mPreview, mContext, mBgHandler);
}
} else {
if (mImpl instanceof Camera1) {
@ -281,7 +301,7 @@ public class CameraView extends FrameLayout {
if (wasOpened) {
stop();
}
mImpl = new Camera1(mCallbacks, mImpl.mPreview);
mImpl = new Camera1(mCallbacks, mImpl.mPreview, mBgHandler);
}
start();
}
@ -298,7 +318,7 @@ public class CameraView extends FrameLayout {
//store the state and restore this state after fall back to Camera1
Parcelable state=onSaveInstanceState();
// Camera2 uses legacy hardware layer; fall back to Camera1
mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()));
mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()), mBgHandler);
onRestoreInstanceState(state);
mImpl.start();
}

View File

@ -19,6 +19,7 @@ package com.google.android.cameraview;
import android.media.CamcorderProfile;
import android.view.View;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import com.facebook.react.bridge.ReadableMap;
@ -31,12 +32,19 @@ import java.util.SortedSet;
abstract class CameraViewImpl {
protected final Callback mCallback;
protected final PreviewImpl mPreview;
CameraViewImpl(Callback callback, PreviewImpl preview) {
// Background handler that the implementation an use to run heavy tasks in background
// in a thread/looper provided by the view.
// Most calls should not require this since the view will already schedule it
// on the bg thread. However, the implementation might need to do some heavy work
// by itself.
protected final Handler mBgHandler;
CameraViewImpl(Callback callback, PreviewImpl preview, Handler bgHandler) {
mCallback = callback;
mPreview = preview;
mBgHandler = bgHandler;
}
View getView() {

View File

@ -270,11 +270,8 @@ public class CameraModule extends ReactContextBaseJavaModule {
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
}
}
catch(IllegalStateException e){
promise.reject("E_CAMERA_UNAVAILABLE", e.getMessage());
}
catch (Exception e) {
promise.reject("E_CAMERA_BAD_VIEWTAG", e.getMessage());
promise.reject("E_TAKE_PICTURE_FAILED", e.getMessage());
}
}
});

View File

@ -43,7 +43,7 @@ public class CameraViewManager extends ViewGroupManager<RNCameraView> {
@Override
public void onDropViewInstance(RNCameraView view) {
view.stop();
view.onHostDestroy();
super.onDropViewInstance(view);
}

View File

@ -226,22 +226,28 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
mPlaySoundOnCapture = playSoundOnCapture;
}
public void takePicture(ReadableMap options, final Promise promise, File cacheDirectory) {
mPictureTakenPromises.add(promise);
mPictureTakenOptions.put(promise, options);
mPictureTakenDirectories.put(promise, cacheDirectory);
if (mPlaySoundOnCapture) {
MediaActionSound sound = new MediaActionSound();
sound.play(MediaActionSound.SHUTTER_CLICK);
}
try {
super.takePicture(options);
} catch (Exception e) {
mPictureTakenPromises.remove(promise);
mPictureTakenOptions.remove(promise);
mPictureTakenDirectories.remove(promise);
throw e;
}
public void takePicture(final ReadableMap options, final Promise promise, final File cacheDirectory) {
mBgHandler.post(new Runnable() {
@Override
public void run() {
mPictureTakenPromises.add(promise);
mPictureTakenOptions.put(promise, options);
mPictureTakenDirectories.put(promise, cacheDirectory);
if (mPlaySoundOnCapture) {
MediaActionSound sound = new MediaActionSound();
sound.play(MediaActionSound.SHUTTER_CLICK);
}
try {
RNCameraView.super.takePicture(options);
} catch (Exception e) {
mPictureTakenPromises.remove(promise);
mPictureTakenOptions.remove(promise);
mPictureTakenDirectories.remove(promise);
promise.reject("E_TAKE_PICTURE_FAILED", e.getMessage());
}
}
});
}
@Override
@ -249,39 +255,44 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
RNCameraViewHelper.emitPictureSavedEvent(this, response);
}
public void record(ReadableMap options, final Promise promise, File cacheDirectory) {
try {
String path = options.hasKey("path") ? options.getString("path") : RNFileUtils.getOutputFilePath(cacheDirectory, ".mp4");
int maxDuration = options.hasKey("maxDuration") ? options.getInt("maxDuration") : -1;
int maxFileSize = options.hasKey("maxFileSize") ? options.getInt("maxFileSize") : -1;
public void record(final ReadableMap options, final Promise promise, final File cacheDirectory) {
mBgHandler.post(new Runnable() {
@Override
public void run() {
try {
String path = options.hasKey("path") ? options.getString("path") : RNFileUtils.getOutputFilePath(cacheDirectory, ".mp4");
int maxDuration = options.hasKey("maxDuration") ? options.getInt("maxDuration") : -1;
int maxFileSize = options.hasKey("maxFileSize") ? options.getInt("maxFileSize") : -1;
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
if (options.hasKey("quality")) {
profile = RNCameraViewHelper.getCamcorderProfile(options.getInt("quality"));
}
if (options.hasKey("videoBitrate")) {
profile.videoBitRate = options.getInt("videoBitrate");
}
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
if (options.hasKey("quality")) {
profile = RNCameraViewHelper.getCamcorderProfile(options.getInt("quality"));
}
if (options.hasKey("videoBitrate")) {
profile.videoBitRate = options.getInt("videoBitrate");
}
boolean recordAudio = true;
if (options.hasKey("mute")) {
recordAudio = !options.getBoolean("mute");
}
boolean recordAudio = true;
if (options.hasKey("mute")) {
recordAudio = !options.getBoolean("mute");
}
int orientation = Constants.ORIENTATION_AUTO;
if (options.hasKey("orientation")) {
orientation = options.getInt("orientation");
}
int orientation = Constants.ORIENTATION_AUTO;
if (options.hasKey("orientation")) {
orientation = options.getInt("orientation");
}
if (super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile, orientation)) {
mIsRecording = true;
mVideoRecordedPromise = promise;
} else {
promise.reject("E_RECORDING_FAILED", "Starting video recording failed. Another recording might be in progress.");
if (RNCameraView.super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile, orientation)) {
mIsRecording = true;
mVideoRecordedPromise = promise;
} else {
promise.reject("E_RECORDING_FAILED", "Starting video recording failed. Another recording might be in progress.");
}
} catch (IOException e) {
promise.reject("E_RECORDING_FAILED", "Starting video recording failed - could not create video file.");
}
}
} catch (IOException e) {
promise.reject("E_RECORDING_FAILED", "Starting video recording failed - could not create video file.");
}
});
}
/**
@ -476,11 +487,16 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
@Override
public void onHostResume() {
if (hasCameraPermissions()) {
if ((mIsPaused && !isCameraOpened()) || mIsNew) {
mIsPaused = false;
mIsNew = false;
start();
}
mBgHandler.post(new Runnable() {
@Override
public void run() {
if ((mIsPaused && !isCameraOpened()) || mIsNew) {
mIsPaused = false;
mIsNew = false;
start();
}
}
});
} else {
RNCameraViewHelper.emitMountErrorEvent(this, "Camera permissions not granted - component could not be rendered.");
}
@ -508,6 +524,8 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
mMultiFormatReader = null;
stop();
mThemedReactContext.removeLifecycleEventListener(this);
this.cleanup();
}
private boolean hasCameraPermissions() {