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* - *option to pass a `File` when recording a video*
- *introduced a smart measuring and sizing behavior, replacing bugged `adjustViewBounds`* - *introduced a smart measuring and sizing behavior, replacing bugged `adjustViewBounds`*
- *measure `CameraView` as center crop or center inside* - *measure `CameraView` as center crop or center inside*
- *add multiple `CameraListener`s for events*
*Feel free to open issues with suggestions or contribute.* *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 # 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 - System permission handling
- Dynamic sizing behavior - Dynamic sizing behavior
- Create a `CameraView` of any size (not just presets!) - 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 - EXIF support
- Automatically detected orientation tag - Automatically detected orientation tag
- Plug in location tags with `CameraView.setLocation(double, double)` - Plug in location tags with `CameraView.setLocation(double, double)`
- Control the camera parameters via XML or programmatically
- TODO: Built-in pinch to zoom - TODO: Built-in pinch to zoom
## Setup ## Setup
For now, clone the repo and add it to your project. For now, you must clone the repo and add it to your project.
TODO: publish to bintray.
## Usage ## Usage
@ -379,12 +380,12 @@ If you don't request this feature, you can use `CameraKit.hasCameras()` to detec
## Roadmap ## 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 - [ ] fix CropOutput class presumably not working on rotated pictures
- [ ] test video and 'frame' capture behavior, I expect some bugs there - [ ] test video and 'frame' capture behavior, I expect some bugs there
- [ ] simple APIs to draw grid lines - [ ] 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` - [ ] 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 - [ ] pinch to zoom support
- [ ] exposure correction APIs - [ ] 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 - [ ] EXIF support for 'frame' captured pictures, using ExifInterface library, so we can stop rotating it in Java
- [ ] add onRequestPermissionResults for easy permission callback - [ ] add onRequestPermissionResults for easy permission callback
- [ ] better error handling, maybe with a onError(e) method in the public listener - [ ] 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(); private final Object mLock = new Object();
Camera1(CameraView.CameraListenerWrapper callback, final PreviewImpl preview) { Camera1(CameraView.CameraCallbacks callback, final PreviewImpl preview) {
super(callback, 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); super(callback, preview);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);

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

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

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

Loading…
Cancel
Save