Add cameraUseDeviceOrientation flag

pull/497/head
Mattia Iavarone 6 years ago
parent 71f006c6d9
commit b047cdd4db
  1. 35
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  2. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  3. 39
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  4. 38
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  5. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Step.java
  6. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  7. 31
      cameraview/src/main/java/com/otaliastudios/cameraview/video/Full2VideoRecorder.java
  8. 2
      cameraview/src/main/res/values/attrs.xml
  9. 1
      demo/src/main/res/layout/activity_camera.xml

@ -101,6 +101,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Self managed parameters // Self managed parameters
private boolean mPlaySounds; private boolean mPlaySounds;
private boolean mUseDeviceOrientation;
private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4); private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4);
private Preview mPreview; private Preview mPreview;
private Engine mEngine; private Engine mEngine;
@ -152,6 +153,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Self managed // Self managed
boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS); boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS);
boolean useDeviceOrientation = a.getBoolean(R.styleable.CameraView_cameraUseDeviceOrientation, true);
mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false); mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false);
mPreview = controls.getPreview(); mPreview = controls.getPreview();
mEngine = controls.getEngine(); mEngine = controls.getEngine();
@ -192,6 +194,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Apply self managed // Apply self managed
setPlaySounds(playSounds); setPlaySounds(playSounds);
setUseDeviceOrientation(useDeviceOrientation);
setGrid(controls.getGrid()); setGrid(controls.getGrid());
setGridColor(gridColor); setGridColor(gridColor);
@ -640,7 +643,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
if (checkPermissions(getAudio())) { if (checkPermissions(getAudio())) {
// Update display orientation for current CameraEngine // Update display orientation for current CameraEngine
mOrientationHelper.enable(getContext()); mOrientationHelper.enable(getContext());
mCameraEngine.setDisplayOffset(mOrientationHelper.getDisplayOffset()); mCameraEngine.getAngles().setDisplayOffset(mOrientationHelper.getDisplayOffset());
mCameraEngine.start(); mCameraEngine.start();
} }
} }
@ -1648,6 +1651,27 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
return mPlaySounds; return mPlaySounds;
} }
/**
* Controls whether picture and video output should consider the current device orientation.
* For example, when true, if the user rotates the device before taking a picture, the picture
* will be rotated as well.
*
* @param useDeviceOrientation true to consider device orientation for outputs
*/
public void setUseDeviceOrientation(boolean useDeviceOrientation) {
mUseDeviceOrientation = useDeviceOrientation;
}
/**
* Gets the current behavior for considering the device orientation when returning picture
* or video outputs.
*
* @see #setUseDeviceOrientation(boolean)
* @return whether we are using the device orientation for outputs
*/
public boolean getUseDeviceOrientation() {
return mUseDeviceOrientation;
}
/** /**
* Sets the encoder for video recordings. * Sets the encoder for video recordings.
@ -1890,8 +1914,15 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@Override @Override
public void onDeviceOrientationChanged(int deviceOrientation) { public void onDeviceOrientationChanged(int deviceOrientation) {
mLogger.i("onDeviceOrientationChanged", deviceOrientation); mLogger.i("onDeviceOrientationChanged", deviceOrientation);
mCameraEngine.setDeviceOrientation(deviceOrientation);
int displayOffset = mOrientationHelper.getDisplayOffset(); int displayOffset = mOrientationHelper.getDisplayOffset();
if (!mUseDeviceOrientation) {
// To fool the engine to return outputs in the VIEW reference system,
// The device orientation should be set to -displayOffset.
int fakeDeviceOrientation = (360 - displayOffset) % 360;
mCameraEngine.getAngles().setDeviceOrientation(fakeDeviceOrientation);
} else {
mCameraEngine.getAngles().setDeviceOrientation(deviceOrientation);
}
final int value = (deviceOrientation + displayOffset) % 360; final int value = (deviceOrientation + displayOffset) % 360;
mUiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override

@ -123,7 +123,7 @@ public class Camera1Engine extends CameraEngine implements
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) { for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, cameraInfo); Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == internalFacing) { if (cameraInfo.facing == internalFacing) {
setSensorOffset(facing, cameraInfo.orientation); getAngles().setSensorOffset(facing, cameraInfo.orientation);
mCameraId = i; mCameraId = i;
return true; return true;
} }
@ -351,13 +351,11 @@ public class Camera1Engine extends CameraEngine implements
throw new IllegalStateException("Video snapshots are only supported starting from API 18."); throw new IllegalStateException("Video snapshots are only supported starting from API 18.");
} }
GlCameraPreview glPreview = (GlCameraPreview) mPreview; GlCameraPreview glPreview = (GlCameraPreview) mPreview;
// Output size is easy:
Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT); Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT);
if (outputSize == null) { if (outputSize == null) {
throw new IllegalStateException("outputSize should not be null."); throw new IllegalStateException("outputSize should not be null.");
} }
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; AspectRatio outputRatio = getAngles().flip(Reference.VIEW, Reference.OUTPUT) ? viewAspectRatio.flip() : viewAspectRatio;
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio); Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio);
outputSize = new Size(outputCrop.width(), outputCrop.height()); outputSize = new Size(outputCrop.width(), outputCrop.height());
stub.size = outputSize; stub.size = outputSize;

@ -19,7 +19,6 @@ import android.hardware.camera2.params.StreamConfigurationMap;
import android.location.Location; import android.location.Location;
import android.media.Image; import android.media.Image;
import android.media.ImageReader; import android.media.ImageReader;
import android.media.MediaCodec;
import android.os.Build; import android.os.Build;
import android.util.Rational; import android.util.Rational;
import android.view.Surface; import android.view.Surface;
@ -96,8 +95,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
private Surface mPreviewStreamSurface; private Surface mPreviewStreamSurface;
// Video recording // Video recording
private Surface mFullVideoPersistentSurface; // API 23+. The surface is created before. private VideoResult.Stub mFullVideoPendingStub; // When takeVideo is called, we have to reset the session.
private VideoResult.Stub mFullVideoPendingStub; // API 21-22. When takeVideo is called, we have to reset the session.
// Picture capturing // Picture capturing
private ImageReader mPictureReader; private ImageReader mPictureReader;
@ -302,7 +300,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (internalFacing == readCharacteristic(characteristics, CameraCharacteristics.LENS_FACING, -99)) { if (internalFacing == readCharacteristic(characteristics, CameraCharacteristics.LENS_FACING, -99)) {
mCameraId = cameraId; mCameraId = cameraId;
int sensorOffset = readCharacteristic(characteristics, CameraCharacteristics.SENSOR_ORIENTATION, 0); int sensorOffset = readCharacteristic(characteristics, CameraCharacteristics.SENSOR_ORIENTATION, 0);
setSensorOffset(facing, sensorOffset); getAngles().setSensorOffset(facing, sensorOffset);
return true; return true;
} }
} catch (CameraAccessException ignore) { } catch (CameraAccessException ignore) {
@ -411,11 +409,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// 2. VIDEO RECORDING // 2. VIDEO RECORDING
if (getMode() == Mode.VIDEO) { if (getMode() == Mode.VIDEO) {
if (Full2VideoRecorder.SUPPORTS_PERSISTENT_SURFACE) { if (mFullVideoPendingStub != null) {
mFullVideoPersistentSurface = MediaCodec.createPersistentInputSurface(); Full2VideoRecorder recorder = new Full2VideoRecorder(this, mCameraId);
outputSurfaces.add(mFullVideoPersistentSurface);
} else if (mFullVideoPendingStub != null) {
Full2VideoRecorder recorder = new Full2VideoRecorder(this, mCameraId, null);
try { try {
outputSurfaces.add(recorder.createInputSurface(mFullVideoPendingStub)); outputSurfaces.add(recorder.createInputSurface(mFullVideoPendingStub));
} catch (Full2VideoRecorder.PrepareException e) { } catch (Full2VideoRecorder.PrepareException e) {
@ -567,10 +562,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override @Override
protected Task<Void> onStopBind() { protected Task<Void> onStopBind() {
LOG.i("onStopBind:", "About to clean up."); LOG.i("onStopBind:", "About to clean up.");
if (mFullVideoPersistentSurface != null) {
mFullVideoPersistentSurface.release();
mFullVideoPersistentSurface = null;
}
mFrameProcessingSurface = null; mFrameProcessingSurface = null;
mPreviewStreamSurface = null; mPreviewStreamSurface = null;
mPreviewStreamSize = null; mPreviewStreamSize = null;
@ -667,19 +658,17 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
LOG.i("onTakeVideo", "called."); LOG.i("onTakeVideo", "called.");
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
stub.size = getAngles().flip(Reference.SENSOR, Reference.OUTPUT) ? mCaptureSize.flip() : mCaptureSize; stub.size = getAngles().flip(Reference.SENSOR, Reference.OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
if (!Full2VideoRecorder.SUPPORTS_PERSISTENT_SURFACE) { // We must restart the session at each time.
// On API 21 and 22, we must restart the session at each time. // Save the pending data and restart the session.
// Save the pending data and restart the session. LOG.w("onTakeVideo", "calling restartBind.");
LOG.w("onTakeVideo", "calling restartBind."); mFullVideoPendingStub = stub;
mFullVideoPendingStub = stub; restartBind();
restartBind();
} else {
doTakeVideo(stub);
}
} }
private void doTakeVideo(@NonNull final VideoResult.Stub stub) { private void doTakeVideo(@NonNull final VideoResult.Stub stub) {
mVideoRecorder = new Full2VideoRecorder(this, mCameraId, mFullVideoPersistentSurface); if (!(mVideoRecorder instanceof Full2VideoRecorder)) {
mVideoRecorder = new Full2VideoRecorder(this, mCameraId);
}
Full2VideoRecorder recorder = (Full2VideoRecorder) mVideoRecorder; Full2VideoRecorder recorder = (Full2VideoRecorder) mVideoRecorder;
try { try {
createRepeatingRequestBuilder(CameraDevice.TEMPLATE_RECORD); createRepeatingRequestBuilder(CameraDevice.TEMPLATE_RECORD);
@ -706,13 +695,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
throw new IllegalStateException("Video snapshots are only supported with GlCameraPreview."); throw new IllegalStateException("Video snapshots are only supported with GlCameraPreview.");
} }
GlCameraPreview glPreview = (GlCameraPreview) mPreview; GlCameraPreview glPreview = (GlCameraPreview) mPreview;
// Output size is easy:
Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT); Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT);
if (outputSize == null) { if (outputSize == null) {
throw new IllegalStateException("outputSize should not be null."); throw new IllegalStateException("outputSize should not be null.");
} }
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; AspectRatio outputRatio = getAngles().flip(Reference.VIEW, Reference.OUTPUT) ? viewAspectRatio.flip() : viewAspectRatio;
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio); Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio);
outputSize = new Size(outputCrop.width(), outputCrop.height()); outputSize = new Size(outputCrop.width(), outputCrop.height());
stub.size = outputSize; stub.size = outputSize;

@ -70,7 +70,7 @@ import java.util.concurrent.TimeUnit;
* So at the end of both step 1 and 2, the engine should check if both have * So at the end of both step 1 and 2, the engine should check if both have
* been performed and trigger the steps 3 and 4. * been performed and trigger the steps 3 and 4.
* *
* We use an abstraction for each step called {@link CameraEngineStep} that manages the state of * We use an abstraction for each step called {@link Step} that manages the state of
* each step and ensures that start and stop operations, for each step, are never called if the * each step and ensures that start and stop operations, for each step, are never called if the
* previous one has not ended. * previous one has not ended.
* *
@ -140,11 +140,11 @@ public abstract class CameraEngine implements
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public static final int STATE_STOPPING = CameraEngineStep.STATE_STOPPING; public static final int STATE_STOPPING = Step.STATE_STOPPING;
public static final int STATE_STOPPED = CameraEngineStep.STATE_STOPPED; public static final int STATE_STOPPED = Step.STATE_STOPPED;
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public static final int STATE_STARTING = CameraEngineStep.STATE_STARTING; public static final int STATE_STARTING = Step.STATE_STARTING;
public static final int STATE_STARTED = CameraEngineStep.STATE_STARTED; public static final int STATE_STARTED = Step.STATE_STARTED;
// Need to be protected // Need to be protected
@SuppressWarnings("WeakerAccess") protected WorkerHandler mHandler; @SuppressWarnings("WeakerAccess") protected WorkerHandler mHandler;
@ -185,16 +185,17 @@ public abstract class CameraEngine implements
private int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors private int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
// Steps // Steps
private final CameraEngineStep.Callback mStepCallback = new CameraEngineStep.Callback() { private final Step.Callback mStepCallback = new Step.Callback() {
@Override @NonNull public Executor getExecutor() { return mHandler.getExecutor(); } @Override @NonNull public Executor getExecutor() { return mHandler.getExecutor(); }
@Override public void handleException(@NonNull Exception exception) { @Override public void handleException(@NonNull Exception exception) {
CameraEngine.this.handleException(Thread.currentThread(), exception, false); CameraEngine.this.handleException(Thread.currentThread(), exception, false);
} }
}; };
@VisibleForTesting CameraEngineStep mEngineStep = new CameraEngineStep("engine", mStepCallback); @VisibleForTesting
private CameraEngineStep mBindStep = new CameraEngineStep("bind", mStepCallback); Step mEngineStep = new Step("engine", mStepCallback);
private CameraEngineStep mPreviewStep = new CameraEngineStep("preview", mStepCallback); private Step mBindStep = new Step("bind", mStepCallback);
private CameraEngineStep mAllStep = new CameraEngineStep("all", mStepCallback); private Step mPreviewStep = new Step("preview", mStepCallback);
private Step mAllStep = new Step("all", mStepCallback);
// Ops used for testing. // Ops used for testing.
@VisibleForTesting Op<Void> mStartVideoOp = new Op<>(); @VisibleForTesting Op<Void> mStartVideoOp = new Op<>();
@ -785,21 +786,6 @@ public abstract class CameraEngine implements
return mAngles; return mAngles;
} }
@SuppressWarnings("WeakerAccess")
protected final void setSensorOffset(@NonNull Facing facing, int sensorOffset) {
mAngles.setSensorOffset(facing, sensorOffset);
}
// This is called before start() and never again.
public final void setDisplayOffset(int displayOffset) {
mAngles.setDisplayOffset(displayOffset);
}
// This can be called multiple times.
public final void setDeviceOrientation(int deviceOrientation) {
mAngles.setDeviceOrientation(deviceOrientation);
}
public final void setPreviewStreamSizeSelector(@Nullable SizeSelector selector) { public final void setPreviewStreamSizeSelector(@Nullable SizeSelector selector) {
mPreviewStreamSizeSelector = selector; mPreviewStreamSizeSelector = selector;
} }
@ -1018,7 +1004,7 @@ public abstract class CameraEngine implements
* Camera is about to be opened. Implementors should look into available cameras * Camera is about to be opened. Implementors should look into available cameras
* and see if anyone matches the given {@link Facing value}. * and see if anyone matches the given {@link Facing value}.
* *
* If so, implementors should set {@link #setSensorOffset(Facing, int)} and any other information * If so, implementors should set {@link Angles#setSensorOffset(Facing, int)} and any other information
* (like camera ID) needed to start the engine. * (like camera ID) needed to start the engine.
* *
* @param facing the facing value * @param facing the facing value

@ -34,9 +34,9 @@ import androidx.annotation.VisibleForTesting;
* *
* <strong>This class is NOT thread safe!</string> * <strong>This class is NOT thread safe!</string>
*/ */
class CameraEngineStep { class Step {
private static final String TAG = CameraEngineStep.class.getSimpleName(); private static final String TAG = Step.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
interface Callback { interface Callback {
@ -59,7 +59,7 @@ class CameraEngineStep {
private final String name; private final String name;
private final Callback callback; private final Callback callback;
CameraEngineStep(@NonNull String name, @NonNull Callback callback) { Step(@NonNull String name, @NonNull Callback callback) {
this.name = name.toUpperCase(); this.name = name.toUpperCase();
this.callback = callback; this.callback = callback;
} }

@ -114,7 +114,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
Matrix.scaleM(mTransform, 0, realScaleX, realScaleY, 1); Matrix.scaleM(mTransform, 0, realScaleX, realScaleY, 1);
// Fix rotation: // Fix rotation:
// TODO Not sure why we need the minus here... It makes no sense to me. // Not sure why we need the minus here... It makes no sense to me.
LOG.w("Recording frame. Rotation:", mResult.rotation, "Actual:", -mResult.rotation); LOG.w("Recording frame. Rotation:", mResult.rotation, "Actual:", -mResult.rotation);
int rotation = -mResult.rotation; int rotation = -mResult.rotation;
mResult.rotation = 0; mResult.rotation = 0;

@ -1,14 +1,6 @@
package com.otaliastudios.cameraview.video; package com.otaliastudios.cameraview.video;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.media.Image;
import android.media.MediaActionSound;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import android.os.Build; import android.os.Build;
import android.view.Surface; import android.view.Surface;
@ -33,21 +25,13 @@ public class Full2VideoRecorder extends FullVideoRecorder {
private static final String TAG = Full2VideoRecorder.class.getSimpleName(); private static final String TAG = Full2VideoRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
// This actually didn't work as I expected, we're never using this. Should remove.
@SuppressWarnings("PointlessBooleanExpression") // TODO
public static final boolean SUPPORTS_PERSISTENT_SURFACE = false && Build.VERSION.SDK_INT >= 23;
private final String mCameraId; private final String mCameraId;
private Surface mInputSurface; private Surface mInputSurface;
private final boolean mUseInputSurface;
public Full2VideoRecorder(@NonNull Camera2Engine engine, public Full2VideoRecorder(@NonNull Camera2Engine engine,
@NonNull String cameraId, @NonNull String cameraId) {
@Nullable Surface surface) {
super(engine); super(engine);
mCameraId = cameraId; mCameraId = cameraId;
mUseInputSurface = surface != null && SUPPORTS_PERSISTENT_SURFACE;
mInputSurface = mUseInputSurface ? surface : null;
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")
@ -56,10 +40,10 @@ public class Full2VideoRecorder extends FullVideoRecorder {
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
Size size = stub.rotation % 180 != 0 ? stub.size.flip() : stub.size; Size size = stub.rotation % 180 != 0 ? stub.size.flip() : stub.size;
mProfile = CamcorderProfiles.get(mCameraId, size); mProfile = CamcorderProfiles.get(mCameraId, size);
if (mUseInputSurface) { // This was an option: get the surface from outside this class, using MediaCodec.createPersistentInputSurface()
//noinspection ConstantConditions // But it doesn't really help since the Camera2 engine refuses a surface that has not been configured,
mediaRecorder.setInputSurface(mInputSurface); // so even with that trick we would have to attach the surface to this recorder before creating the CameraSession.
} // mediaRecorder.setInputSurface(mInputSurface);
return super.onPrepareMediaRecorder(stub, mediaRecorder); return super.onPrepareMediaRecorder(stub, mediaRecorder);
} }
@ -72,9 +56,6 @@ public class Full2VideoRecorder extends FullVideoRecorder {
*/ */
@NonNull @NonNull
public Surface createInputSurface(@NonNull VideoResult.Stub stub) throws PrepareException { public Surface createInputSurface(@NonNull VideoResult.Stub stub) throws PrepareException {
if (mUseInputSurface) {
throw new IllegalStateException("We are using the input surface (API23+), can't createInputSurface here.");
}
if (!prepareMediaRecorder(stub)) { if (!prepareMediaRecorder(stub)) {
throw new PrepareException(mError); throw new PrepareException(mError);
} }
@ -87,8 +68,6 @@ public class Full2VideoRecorder extends FullVideoRecorder {
return mInputSurface; return mInputSurface;
} }
@SuppressWarnings("WeakerAccess")
public class PrepareException extends Exception { public class PrepareException extends Exception {
private PrepareException(Throwable cause) { private PrepareException(Throwable cause) {
super(cause); super(cause);

@ -131,5 +131,7 @@
<attr name="cameraAutoFocusMarker" format="string|reference"/> <attr name="cameraAutoFocusMarker" format="string|reference"/>
<attr name="cameraUseDeviceOrientation" format="boolean"/>
</declare-styleable> </declare-styleable>
</resources> </resources>

@ -17,6 +17,7 @@
android:layout_marginBottom="88dp" android:layout_marginBottom="88dp"
android:keepScreenOn="true" android:keepScreenOn="true"
app:cameraExperimental="true" app:cameraExperimental="true"
app:cameraUseDeviceOrientation="false"
app:cameraEngine="camera2" app:cameraEngine="camera2"
app:cameraPreview="glSurface" app:cameraPreview="glSurface"
app:cameraPlaySounds="true" app:cameraPlaySounds="true"

Loading…
Cancel
Save