Add multiple CameraListeners, callback always on the UI thread

pull/1/head
Mattia Iavarone 7 years ago
parent b48b865379
commit 6a0795c1b3
  1. 13
      README.md
  2. 2
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  3. 2
      camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java
  4. 5
      camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java
  5. 139
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  6. 6
      demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java

@ -11,6 +11,7 @@
- *option to pass a `File` when recording a video*
- *introduced a smart measuring and sizing behavior, replacing bugged `adjustViewBounds`*
- *measure `CameraView` as center crop or center inside*
- *add multiple `CameraListener`s for events*
*Feel free to open issues with suggestions or contribute.*
@ -46,7 +47,7 @@ CameraKit is an easy to use utility to work with the Android Camera APIs. Everyt
# Features
- Image and video capture seamlessly working with the same preview session. (TODO: remove this, use different sessions)
- Seamless image and video capturing, even within the same session
- System permission handling
- Dynamic sizing behavior
- Create a `CameraView` of any size (not just presets!)
@ -59,12 +60,12 @@ CameraKit is an easy to use utility to work with the Android Camera APIs. Everyt
- EXIF support
- Automatically detected orientation tag
- Plug in location tags with `CameraView.setLocation(double, double)`
- Control the camera parameters via XML or programmatically
- TODO: Built-in pinch to zoom
## Setup
For now, clone the repo and add it to your project.
TODO: publish to bintray.
For now, you must clone the repo and add it to your project.
## Usage
@ -379,12 +380,12 @@ If you don't request this feature, you can use `CameraKit.hasCameras()` to detec
## Roadmap
This are things that need to be done, off the top of my head:
These are things that need to be done, off the top of my head:
- [ ] fix CropOutput class presumably not working on rotated pictures
- [ ] test video and 'frame' capture behavior, I expect some bugs there
- [ ] simple APIs to draw grid lines
- [ ] replace setCameraListener() with addCameraListener()
- [x] replace setCameraListener() with addCameraListener()
- [ ] add a `sizingMethod` API to choose the capture size? Could be `max`, `4:3`, `16:9`... Right now it's `max`
- [ ] pinch to zoom support
- [ ] exposure correction APIs
@ -393,5 +394,5 @@ This are things that need to be done, off the top of my head:
- [ ] EXIF support for 'frame' captured pictures, using ExifInterface library, so we can stop rotating it in Java
- [ ] add onRequestPermissionResults for easy permission callback
- [ ] better error handling, maybe with a onError(e) method in the public listener
- [ ] better threading, for example ensure callbacks are called in the ui thread
- [x] better threading, for example ensure callbacks are called in the ui thread

@ -66,7 +66,7 @@ class Camera1 extends CameraImpl {
private final Object mLock = new Object();
Camera1(CameraView.CameraListenerWrapper callback, final PreviewImpl preview) {
Camera1(CameraView.CameraCallbacks callback, final PreviewImpl preview) {
super(callback, preview);
}

@ -47,7 +47,7 @@ class Camera2 extends CameraImpl {
}
Camera2(CameraView.CameraListenerWrapper callback, PreviewImpl preview, Context context) {
Camera2(CameraView.CameraCallbacks callback, PreviewImpl preview, Context context) {
super(callback, preview);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);

@ -1,6 +1,5 @@
package com.flurgle.camerakit;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.MotionEvent;
@ -9,10 +8,10 @@ import java.io.File;
abstract class CameraImpl implements PreviewImpl.SurfaceCallback {
protected final CameraView.CameraListenerWrapper mCameraListener;
protected final CameraView.CameraCallbacks mCameraListener;
protected final PreviewImpl mPreview;
CameraImpl(CameraView.CameraListenerWrapper callback, PreviewImpl preview) {
CameraImpl(CameraView.CameraCallbacks callback, PreviewImpl preview) {
mCameraListener = callback;
mPreview = preview;
mPreview.setSurfaceCallback(this);

@ -15,15 +15,14 @@ import android.graphics.Rect;
import android.graphics.YuvImage;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
@ -95,7 +94,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@WhiteBalance private int mWhiteBalance;
private int mJpegQuality;
private boolean mCropOutput;
private CameraListenerWrapper mCameraListener;
private CameraCallbacks mCameraCallbacks;
private OrientationHelper mOrientationHelper;
private CameraImpl mCameraImpl;
private PreviewImpl mPreviewImpl;
@ -128,9 +127,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mCropOutput = a.getBoolean(R.styleable.CameraView_cameraCropOutput, CameraKit.Defaults.DEFAULT_CROP_OUTPUT);
a.recycle();
mCameraListener = new CameraListenerWrapper();
mCameraCallbacks = new CameraCallbacks();
mPreviewImpl = new TextureViewPreview(context, this);
mCameraImpl = new Camera1(mCameraListener, mPreviewImpl);
mCameraImpl = new Camera1(mCameraCallbacks, mPreviewImpl);
mFocusMarkerLayout = new FocusMarkerLayout(context);
addView(mFocusMarkerLayout);
@ -463,7 +462,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
}
public void destroy() {
mCameraListener = new CameraListenerWrapper(); // Release inner listener.
mCameraCallbacks.clearListeners(); // Release inner listener.
// This might be useless, but no time to think about it now.
sWorkerHandler = null;
}
@ -724,18 +723,56 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
/**
* Sets a {@link CameraListener} instance to be notified of all
* interesting events that will happen during the camera lifecycle.
*
* @param cameraListener a listener for events.
* @deprecated use {@link #addCameraListener(CameraListener)} instead.
*/
@Deprecated
public void setCameraListener(CameraListener cameraListener) {
this.mCameraListener.wrapListener(cameraListener);
mCameraCallbacks.clearListeners();
if (cameraListener != null) {
mCameraCallbacks.addListener(cameraListener);
}
}
/**
* Adds a {@link CameraListener} instance to be notified of all
* interesting events that happen during the camera lifecycle.
*
* @param cameraListener a listener for events.
*/
public void addCameraListener(CameraListener cameraListener) {
if (cameraListener != null) {
mCameraCallbacks.addListener(cameraListener);
}
}
/**
* Remove a {@link CameraListener} that was previously registered.
*
* @param cameraListener a listener for events.
*/
public void removeCameraListener(CameraListener cameraListener) {
if (cameraListener != null) {
mCameraCallbacks.removeListener(cameraListener);
}
}
/**
* Clears the list of {@link CameraListener} that are registered
* to camera events.
*/
public void clearCameraListeners() {
mCameraCallbacks.clearListeners();
}
public void captureImage() {
mCameraImpl.captureImage();
}
@ -817,20 +854,38 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
}
}
class CameraListenerWrapper extends CameraListener {
class CameraCallbacks extends CameraListener {
private CameraListener mWrappedListener;
private ArrayList<CameraListener> mListeners;
private Handler uiHandler;
public CameraCallbacks() {
mListeners = new ArrayList<>(2);
uiHandler = new Handler(Looper.getMainLooper());
}
@Override
public void onCameraOpened() {
if (mWrappedListener == null) return;
mWrappedListener.onCameraOpened();
uiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
listener.onCameraOpened();
}
}
});
}
@Override
public void onCameraClosed() {
if (mWrappedListener == null) return;
mWrappedListener.onCameraClosed();
uiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
listener.onCameraClosed();
}
}
});
}
public void onCameraPreviewSizeChanged() {
@ -843,35 +898,67 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@Override
public void onPictureTaken(byte[] jpeg) {
if (mWrappedListener == null) return;
if (mCropOutput) {
// TODO cropOutput won't work if image is rotated (e.g. byte[] contains exif orientation).
AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight());
mWrappedListener.onPictureTaken(new CenterCrop(jpeg, outputRatio, mJpegQuality).getJpeg());
} else {
mWrappedListener.onPictureTaken(jpeg);
jpeg = new CenterCrop(jpeg, outputRatio, mJpegQuality).getJpeg();
}
final byte[] data = jpeg;
uiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
listener.onPictureTaken(data);
}
}
});
}
public void processYuvImage(YuvImage yuv) {
if (mWrappedListener == null) return;
byte[] jpeg;
if (mCropOutput) {
AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight());
mWrappedListener.onPictureTaken(new CenterCrop(yuv, outputRatio, mJpegQuality).getJpeg());
jpeg = new CenterCrop(yuv, outputRatio, mJpegQuality).getJpeg();
} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out);
mWrappedListener.onPictureTaken(out.toByteArray());
jpeg = out.toByteArray();
}
final byte[] data = jpeg;
uiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
listener.onPictureTaken(data);
}
}
});
}
@Override
public void onVideoTaken(File video) {
mWrappedListener.onVideoTaken(video);
public void onVideoTaken(final File video) {
uiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
listener.onVideoTaken(video);
}
}
});
}
private void addListener(@NonNull CameraListener cameraListener) {
mListeners.add(cameraListener);
}
private void removeListener(@NonNull CameraListener cameraListener) {
mListeners.remove(cameraListener);
}
private void wrapListener(@Nullable CameraListener cameraListener) {
mWrappedListener = cameraListener;
private void clearListeners() {
mListeners.clear();
}
}

@ -114,7 +114,8 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
if (mCapturing) return;
mCapturing = true;
final long startTime = System.currentTimeMillis();
camera.setCameraListener(new CameraListener() {
camera.clearCameraListeners();
camera.addCameraListener(new CameraListener() {
@Override
public void onPictureTaken(byte[] jpeg) {
super.onPictureTaken(jpeg);
@ -138,7 +139,8 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
}
if (mCapturing) return;
mCapturing = true;
camera.setCameraListener(new CameraListener() {
camera.clearCameraListeners();
camera.addCameraListener(new CameraListener() {
@Override
public void onVideoTaken(File video) {
super.onVideoTaken(video);

Loading…
Cancel
Save