diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index 9a39cddb..c10abfc8 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -20,6 +20,7 @@ import com.otaliastudios.cameraview.controls.Control; import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; +import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.engine.Mapper; import com.otaliastudios.cameraview.gesture.GestureAction; import com.otaliastudios.cameraview.controls.Grid; @@ -309,6 +310,8 @@ public class CameraOptions { return (Collection) getSupportedWhiteBalance(); } else if (controlClass.equals(Engine.class)) { return (Collection) Arrays.asList(Engine.values()); + } else if (controlClass.equals(Preview.class)) { + return (Collection) Arrays.asList(Preview.values()); } // Unrecognized control. return Collections.emptyList(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index abd155eb..4214c49e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -6,6 +6,7 @@ import android.annotation.TargetApi; import android.app.Activity; import androidx.annotation.VisibleForTesting; +import androidx.core.view.ViewCompat; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; @@ -782,9 +783,26 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * @param preview desired preview engine */ public void setPreview(@NonNull Preview preview) { + boolean isNew = preview != mPreview; + if (!isNew) return; mPreview = preview; + if (!ViewCompat.isAttachedToWindow(this) && mCameraPreview != null) { + // Null the preview: will create another when re-attaching. + mCameraPreview.onDestroy(); + mCameraPreview = null; + } } + /** + * Returns the current preview control. + * + * @see #setPreview(Preview) + * @return the current preview control + */ + @NonNull + public Preview getPreview() { + return mPreview; + } /** * Controls the core engine. Should only be called @@ -820,6 +838,16 @@ public class CameraView extends FrameLayout implements LifecycleObserver { setAutoFocusResetDelay(oldEngine.getAutoFocusResetDelay()); } + /** + * Returns the current engine control. + * + * @see #setEngine(Engine) + * @return the current engine control + */ + @NonNull + public Engine getEngine() { + return mEngine; + } /** * Returns a {@link CameraOptions} instance holding supported options for this camera diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java index fab49ca5..e3b5cee9 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java @@ -186,7 +186,9 @@ public abstract class CameraPreview { if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) { crop(mCropOp); } - mSurfaceCallback.onSurfaceAvailable(); + if (mSurfaceCallback != null) { + mSurfaceCallback.onSurfaceAvailable(); + } } /** @@ -203,7 +205,9 @@ public abstract class CameraPreview { if (width > 0 && height > 0) { crop(mCropOp); } - mSurfaceCallback.onSurfaceChanged(); + if (mSurfaceCallback != null) { + mSurfaceCallback.onSurfaceChanged(); + } } } @@ -214,7 +218,9 @@ public abstract class CameraPreview { protected final void dispatchOnSurfaceDestroyed() { mOutputSurfaceWidth = 0; mOutputSurfaceHeight = 0; - mSurfaceCallback.onSurfaceDestroyed(); + if (mSurfaceCallback != null) { + mSurfaceCallback.onSurfaceDestroyed(); + } } /** @@ -233,7 +239,10 @@ public abstract class CameraPreview { * Called by the hosting {@link com.otaliastudios.cameraview.CameraView}, * this is a lifecycle event. */ - public void onDestroy() {} + public void onDestroy() { + ViewGroup parent = (ViewGroup) getRootView().getParent(); + parent.removeView(getRootView()); + } /** * Here we must crop the visible part by applying a scale greater than 1 to one of our diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index ae676cb3..e7c35455 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -21,6 +21,7 @@ import com.otaliastudios.cameraview.CameraView; import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.VideoResult; +import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.size.SizeSelectors; @@ -173,6 +174,10 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis private void capturePictureSnapshot() { if (camera.isTakingPicture()) return; + if (camera.getPreview() != Preview.GL_SURFACE) { + message("Picture snapshots are only allowed with the GL_SURFACE preview.", true); + return; + } mCaptureTime = System.currentTimeMillis(); message("Capturing picture snapshot...", false); camera.takePictureSnapshot(); @@ -193,6 +198,10 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis message("Already taking video.", false); return; } + if (camera.getPreview() != Preview.GL_SURFACE) { + message("Video snapshots are only allowed with the GL_SURFACE preview.", true); + return; + } message("Recording snapshot for 5 seconds...", true); camera.takeVideoSnapshot(new File(getFilesDir(), "video.mp4"), 5000); } @@ -212,10 +221,11 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis @Override public boolean onValueChanged(Control control, Object value, String name) { - if (!camera.isHardwareAccelerated() && (control == Control.WIDTH || control == Control.HEIGHT)) { - if ((Integer) value > 0) { - message("This device does not support hardware acceleration. " + - "In this case you can not change width or height. " + + if ((control == Control.WIDTH || control == Control.HEIGHT)) { + Preview preview = camera.getPreview(); + boolean wrapContent = (Integer) value == ViewGroup.LayoutParams.WRAP_CONTENT; + if (preview == Preview.SURFACE && !wrapContent) { + message("The SurfaceView preview does not support width or height changes. " + "The view will act as WRAP_CONTENT by default.", true); return false; } diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java index 6910362c..41813789 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java @@ -5,10 +5,13 @@ import androidx.annotation.NonNull; import android.view.View; import android.view.ViewGroup; +import com.otaliastudios.cameraview.CameraListener; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.CameraView; +import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Flash; +import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.gesture.GestureAction; import com.otaliastudios.cameraview.controls.Grid; @@ -18,6 +21,7 @@ import com.otaliastudios.cameraview.controls.VideoCodec; import com.otaliastudios.cameraview.controls.WhiteBalance; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -26,28 +30,38 @@ import java.util.List; */ public enum Control { + // Layout WIDTH("Width", false), HEIGHT("Height", true), + // Some controls MODE("Mode", false), FLASH("Flash", false), WHITE_BALANCE("White balance", false), HDR("Hdr", true), - GRID("Grid lines", false), - GRID_COLOR("Grid color", true), + // Engine and preview + ENGINE("Engine", false), + PREVIEW("Preview Surface", true), - // TODO audio bitRate - // TODO video bitRate - // THey are a bit annoying because it's not clear what the default should be. + // Video recording VIDEO_CODEC("Video codec", false), AUDIO("Audio", true), + // TODO audio bitRate + // TODO video bitRate + // They are a bit annoying because it's not clear what the default should be. + // Gestures PINCH("Pinch", false), HSCROLL("Horizontal scroll", false), VSCROLL("Vertical scroll", false), TAP("Single tap", false), - LONG_TAP("Long tap", true); + LONG_TAP("Long tap", true), + + // Others + GRID("Grid lines", false), + GRID_COLOR("Grid color", false), + USE_DEVICE_ORIENTATION("Use device orientation", true); private String name; private boolean last; @@ -88,6 +102,8 @@ public enum Control { case GRID: return options.getSupportedControls(Grid.class); case AUDIO: return options.getSupportedControls(Audio.class); case VIDEO_CODEC: return options.getSupportedControls(VideoCodec.class); + case ENGINE: return options.getSupportedControls(Engine.class); + case PREVIEW: return options.getSupportedControls(Preview.class); case PINCH: case HSCROLL: case VSCROLL: @@ -110,6 +126,8 @@ public enum Control { list3.add(new GridColor(Color.BLACK, "black")); list3.add(new GridColor(Color.YELLOW, "yellow")); return list3; + case USE_DEVICE_ORIENTATION: + return Arrays.asList(true, false); } return null; } @@ -135,11 +153,14 @@ public enum Control { case VSCROLL: return view.getGestureAction(Gesture.SCROLL_VERTICAL); case TAP: return view.getGestureAction(Gesture.TAP); case LONG_TAP: return view.getGestureAction(Gesture.LONG_TAP); + case USE_DEVICE_ORIENTATION: return view.getUseDeviceOrientation(); + case ENGINE: return view.getEngine(); + case PREVIEW: return view.getPreview(); } return null; } - public void applyValue(CameraView camera, Object value) { + public void applyValue(final CameraView camera, final Object value) { switch (this) { case WIDTH: camera.getLayoutParams().width = (int) value; @@ -175,9 +196,61 @@ public enum Control { break; case GRID_COLOR: camera.setGridColor(((GridColor) value).color); + break; + case USE_DEVICE_ORIENTATION: + camera.setUseDeviceOrientation((Boolean) value); + break; + case ENGINE: + boolean started = camera.isOpened(); + if (started) { + camera.addCameraListener(new CameraListener() { + @Override + public void onCameraClosed() { + super.onCameraClosed(); + camera.removeCameraListener(this); + camera.setEngine((Engine) value); + camera.open(); + } + }); + camera.close(); + } else { + camera.setEngine((Engine) value); + } + break; + case PREVIEW: + boolean opened = camera.isOpened(); + if (opened) { + camera.addCameraListener(new CameraListener() { + @Override + public void onCameraClosed() { + super.onCameraClosed(); + camera.removeCameraListener(this); + applyPreview(camera, (Preview) value, true); + } + }); + camera.close(); + } else { + applyPreview(camera, (Preview) value, false); + } } } + // This is really tricky since the preview can only be changed when not attached to window. + private void applyPreview(@NonNull CameraView cameraView, @NonNull Preview newPreview, boolean openWhenDone) { + ViewGroup.LayoutParams params = cameraView.getLayoutParams(); + ViewGroup parent = (ViewGroup) cameraView.getParent(); + int index = 0; + for (int i = 0; i < parent.getChildCount(); i++) { + if (parent.getChildAt(i) == cameraView) { + index = i; + break; + } + } + parent.removeView(cameraView); + cameraView.setPreview(newPreview); + parent.addView(cameraView, index, params); + if (openWhenDone) cameraView.open(); + } static class GridColor { int color; diff --git a/demo/src/main/res/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml index 164432c0..03b195b7 100644 --- a/demo/src/main/res/layout/activity_camera.xml +++ b/demo/src/main/res/layout/activity_camera.xml @@ -17,7 +17,6 @@ android:layout_marginBottom="88dp" android:keepScreenOn="true" app:cameraExperimental="true" - app:cameraUseDeviceOrientation="false" app:cameraEngine="camera2" app:cameraPreview="glSurface" app:cameraPlaySounds="true"