mirror of
https://github.com/status-im/react-native-camera.git
synced 2025-02-24 09:48:17 +00:00
Merge pull request #413 from plougsgaard/android-barcode-scanner-rebased
Android Barcode Scanner
This commit is contained in:
commit
1747ec4b0a
@ -32,4 +32,5 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
compile "com.facebook.react:react-native:0.19.+"
|
||||
compile "com.google.zxing:core:3.2.1"
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ public class RCTCamera {
|
||||
private final HashMap<Integer, CameraInfoWrapper> _cameraInfos;
|
||||
private final HashMap<Integer, Integer> _cameraTypeToIndex;
|
||||
private final Map<Number, Camera> _cameras;
|
||||
private boolean _barcodeScannerEnabled = false;
|
||||
private List<String> _barCodeTypes = null;
|
||||
private int _orientation = -1;
|
||||
private int _actualDeviceOrientation = 0;
|
||||
private int _adjustedDeviceOrientation = 0;
|
||||
@ -134,6 +136,22 @@ public class RCTCamera {
|
||||
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||
}
|
||||
|
||||
public boolean isBarcodeScannerEnabled() {
|
||||
return _barcodeScannerEnabled;
|
||||
}
|
||||
|
||||
public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) {
|
||||
_barcodeScannerEnabled = barcodeScannerEnabled;
|
||||
}
|
||||
|
||||
public List<String> getBarCodeTypes() {
|
||||
return _barCodeTypes;
|
||||
}
|
||||
|
||||
public void setBarCodeTypes(List<String> barCodeTypes) {
|
||||
_barCodeTypes = barCodeTypes;
|
||||
}
|
||||
|
||||
public int getActualDeviceOrientation() {
|
||||
return _actualDeviceOrientation;
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
public static final int MEDIA_TYPE_IMAGE = 1;
|
||||
public static final int MEDIA_TYPE_VIDEO = 2;
|
||||
|
||||
private final ReactApplicationContext _reactContext;
|
||||
private static ReactApplicationContext _reactContext;
|
||||
private RCTSensorOrientationChecker _sensorOrientationChecker;
|
||||
|
||||
private MediaRecorder mMediaRecorder = new MediaRecorder();
|
||||
@ -96,6 +96,10 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
_reactContext.addLifecycleEventListener(this);
|
||||
}
|
||||
|
||||
public static ReactApplicationContext getReactContextSingleton() {
|
||||
return _reactContext;
|
||||
}
|
||||
|
||||
public void onInfo(MediaRecorder mr, int what, int extra) {
|
||||
if ( what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
|
||||
what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
|
||||
|
@ -11,6 +11,8 @@ import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RCTCameraView extends ViewGroup {
|
||||
private final OrientationEventListener _orientationListener;
|
||||
private final Context _context;
|
||||
@ -105,6 +107,14 @@ public class RCTCameraView extends ViewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) {
|
||||
RCTCamera.getInstance().setBarcodeScannerEnabled(barcodeScannerEnabled);
|
||||
}
|
||||
|
||||
public void setBarCodeTypes(List<String> types) {
|
||||
RCTCamera.getInstance().setBarCodeTypes(types);
|
||||
}
|
||||
|
||||
private boolean setActualDeviceOrientation(Context context) {
|
||||
int actualDeviceOrientation = getDeviceOrientation(context);
|
||||
if (_actualDeviceOrientation != actualDeviceOrientation) {
|
||||
|
@ -8,20 +8,43 @@ import android.content.Context;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.Camera;
|
||||
import android.view.TextureView;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
|
||||
class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener {
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener, Camera.PreviewCallback {
|
||||
private int _cameraType;
|
||||
private SurfaceTexture _surfaceTexture;
|
||||
private boolean _isStarting;
|
||||
private boolean _isStopping;
|
||||
private Camera _camera;
|
||||
|
||||
// concurrency lock for barcode scanner to avoid flooding the runtime
|
||||
public static volatile boolean barcodeScannerTaskLock = false;
|
||||
|
||||
// reader instance for the barcode scanner
|
||||
private final MultiFormatReader _multiFormatReader = new MultiFormatReader();
|
||||
|
||||
public RCTCameraViewFinder(Context context, int type) {
|
||||
super(context);
|
||||
this.setSurfaceTextureListener(this);
|
||||
this._cameraType = type;
|
||||
this.initBarcodeReader(RCTCamera.getInstance().getBarCodeTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -112,6 +135,8 @@ class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceText
|
||||
_camera.setParameters(parameters);
|
||||
_camera.setPreviewTexture(_surfaceTexture);
|
||||
_camera.startPreview();
|
||||
// send previews to `onPreviewFrame`
|
||||
_camera.setPreviewCallback(this);
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
@ -129,6 +154,7 @@ class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceText
|
||||
try {
|
||||
if (_camera != null) {
|
||||
_camera.stopPreview();
|
||||
// stop sending previews to `onPreviewFrame`
|
||||
_camera.setPreviewCallback(null);
|
||||
RCTCamera.getInstance().releaseCameraInstance(_cameraType);
|
||||
_camera = null;
|
||||
@ -141,4 +167,144 @@ class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse barcodes as BarcodeFormat constants.
|
||||
*
|
||||
* Supports all iOS codes except [code138, code39mod43, itf14]
|
||||
*
|
||||
* Additionally supports [codabar, code128, maxicode, rss14, rssexpanded, upca, upceanextension]
|
||||
*/
|
||||
private BarcodeFormat parseBarCodeString(String c) {
|
||||
if ("aztec".equals(c)) {
|
||||
return BarcodeFormat.AZTEC;
|
||||
} else if ("ean13".equals(c)) {
|
||||
return BarcodeFormat.EAN_13;
|
||||
} else if ("ean8".equals(c)) {
|
||||
return BarcodeFormat.EAN_8;
|
||||
} else if ("ean8".equals(c)) {
|
||||
return BarcodeFormat.EAN_8;
|
||||
} else if ("qr".equals(c)) {
|
||||
return BarcodeFormat.QR_CODE;
|
||||
} else if ("ean8".equals(c)) {
|
||||
return BarcodeFormat.EAN_8;
|
||||
} else if ("pdf417".equals(c)) {
|
||||
return BarcodeFormat.PDF_417;
|
||||
} else if ("upce".equals(c)) {
|
||||
return BarcodeFormat.UPC_E;
|
||||
} else if ("datamatrix".equals(c)) {
|
||||
return BarcodeFormat.DATA_MATRIX;
|
||||
} else if ("code39".equals(c)) {
|
||||
return BarcodeFormat.CODE_39;
|
||||
} else if ("code93".equals(c)) {
|
||||
return BarcodeFormat.CODE_93;
|
||||
} else if ("interleaved2of5".equals(c)) {
|
||||
return BarcodeFormat.ITF;
|
||||
} else if ("codabar".equals(c)) {
|
||||
return BarcodeFormat.CODABAR;
|
||||
} else if ("code128".equals(c)) {
|
||||
return BarcodeFormat.CODE_128;
|
||||
} else if ("maxicode".equals(c)) {
|
||||
return BarcodeFormat.MAXICODE;
|
||||
} else if ("rss14".equals(c)) {
|
||||
return BarcodeFormat.RSS_14;
|
||||
} else if ("rssexpanded".equals(c)) {
|
||||
return BarcodeFormat.RSS_EXPANDED;
|
||||
} else if ("upca".equals(c)) {
|
||||
return BarcodeFormat.UPC_A;
|
||||
} else if ("upceanextension".equals(c)) {
|
||||
return BarcodeFormat.UPC_EAN_EXTENSION;
|
||||
} else {
|
||||
android.util.Log.v("RCTCamera", "Unsupported code.. [" + c + "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the barcode decoder.
|
||||
*/
|
||||
private void initBarcodeReader(List<String> barCodeTypes) {
|
||||
EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||
EnumSet<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
|
||||
|
||||
if (barCodeTypes != null) {
|
||||
for (String code : barCodeTypes) {
|
||||
BarcodeFormat format = parseBarCodeString(code);
|
||||
if (format != null) {
|
||||
decodeFormats.add(format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
|
||||
_multiFormatReader.setHints(hints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a barcode reader task if
|
||||
* - the barcode scanner is enabled (has a onBarCodeRead function)
|
||||
* - one isn't already running
|
||||
*
|
||||
* See {Camera.PreviewCallback}
|
||||
*/
|
||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||
if (RCTCamera.getInstance().isBarcodeScannerEnabled() && !RCTCameraViewFinder.barcodeScannerTaskLock) {
|
||||
RCTCameraViewFinder.barcodeScannerTaskLock = true;
|
||||
new ReaderAsyncTask(camera, data).execute();
|
||||
}
|
||||
}
|
||||
|
||||
private class ReaderAsyncTask extends AsyncTask<Void, Void, Void> {
|
||||
private byte[] imageData;
|
||||
private final Camera camera;
|
||||
|
||||
ReaderAsyncTask(Camera camera, byte[] imageData) {
|
||||
this.camera = camera;
|
||||
this.imageData = imageData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... ignored) {
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Camera.Size size = camera.getParameters().getPreviewSize();
|
||||
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
// rotate for zxing if orientation is portrait
|
||||
if (RCTCamera.getInstance().getActualDeviceOrientation() == 0) {
|
||||
byte[] rotated = new byte[imageData.length];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
rotated[x * height + height - y - 1] = imageData[x + y * width];
|
||||
}
|
||||
}
|
||||
width = size.height;
|
||||
height = size.width;
|
||||
imageData = rotated;
|
||||
}
|
||||
|
||||
try {
|
||||
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(imageData, width, height, 0, 0, width, height, false);
|
||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||
Result result = _multiFormatReader.decodeWithState(bitmap);
|
||||
|
||||
ReactContext reactContext = RCTCameraModule.getReactContextSingleton();
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putString("data", result.getText());
|
||||
event.putString("type", result.getBarcodeFormat().toString());
|
||||
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("CameraBarCodeReadAndroid", event);
|
||||
|
||||
} catch (Throwable t) {
|
||||
// meh
|
||||
} finally {
|
||||
_multiFormatReader.reset();
|
||||
RCTCameraViewFinder.barcodeScannerTaskLock = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.uimanager.*;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RCTCameraViewManager extends ViewGroupManager<RCTCameraView> {
|
||||
private static final String REACT_CLASS = "RCTCamera";
|
||||
|
||||
@ -61,4 +66,21 @@ public class RCTCameraViewManager extends ViewGroupManager<RCTCameraView> {
|
||||
public void setCaptureAudio(RCTCameraView view, boolean captureAudio) {
|
||||
// TODO - implement video mode
|
||||
}
|
||||
|
||||
@ReactProp(name = "barcodeScannerEnabled")
|
||||
public void setBarcodeScannerEnabled(RCTCameraView view, boolean barcodeScannerEnabled) {
|
||||
view.setBarcodeScannerEnabled(barcodeScannerEnabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "barCodeTypes")
|
||||
public void setBarCodeTypes(RCTCameraView view, ReadableArray barCodeTypes) {
|
||||
if (barCodeTypes == null) {
|
||||
return;
|
||||
}
|
||||
List<String> result = new ArrayList<String>(barCodeTypes.size());
|
||||
for (int i = 0; i < barCodeTypes.size(); i++) {
|
||||
result.add(barCodeTypes.getString(i));
|
||||
}
|
||||
view.setBarCodeTypes(result);
|
||||
}
|
||||
}
|
||||
|
40
index.js
40
index.js
@ -1,6 +1,7 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import {
|
||||
NativeAppEventEmitter,
|
||||
DeviceEventEmitter, // android
|
||||
NativeAppEventEmitter, // ios
|
||||
NativeModules,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
@ -46,6 +47,8 @@ function convertNativeProps(props) {
|
||||
newProps.barCodeTypes = [];
|
||||
}
|
||||
|
||||
newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function'
|
||||
|
||||
return newProps;
|
||||
}
|
||||
|
||||
@ -89,6 +92,7 @@ export default class Camera extends Component {
|
||||
]),
|
||||
keepAwake: PropTypes.bool,
|
||||
onBarCodeRead: PropTypes.func,
|
||||
barcodeScannerEnabled: PropTypes.bool,
|
||||
onFocusChanged: PropTypes.func,
|
||||
onZoomChanged: PropTypes.func,
|
||||
mirrorImage: PropTypes.bool,
|
||||
@ -141,9 +145,9 @@ export default class Camera extends Component {
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
this.cameraBarCodeReadListener = NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead);
|
||||
this._addOnBarCodeReadListener()
|
||||
|
||||
let { captureMode } = convertNativeProps({captureMode: this.props.captureMode})
|
||||
let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode })
|
||||
let hasVideoAndAudio = this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video
|
||||
let check = hasVideoAndAudio ? Camera.checkDeviceAuthorizationStatus : Camera.checkVideoAuthorizationStatus;
|
||||
|
||||
@ -154,13 +158,37 @@ export default class Camera extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.cameraBarCodeReadListener.remove();
|
||||
this._removeOnBarCodeReadListener()
|
||||
|
||||
if (this.state.isRecording) {
|
||||
this.stopCapture();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
const { onBarCodeRead } = this.props
|
||||
if (onBarCodeRead && !newProps.onBarCodeRead) {
|
||||
this._addOnBarCodeReadListener(newProps)
|
||||
}
|
||||
}
|
||||
|
||||
_addOnBarCodeReadListener(props) {
|
||||
const { onBarCodeRead } = props || this.props
|
||||
this._removeOnBarCodeReadListener()
|
||||
if (onBarCodeRead) {
|
||||
this.cameraBarCodeReadListener = Platform.select({
|
||||
ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead),
|
||||
android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead)
|
||||
})
|
||||
}
|
||||
}
|
||||
_removeOnBarCodeReadListener() {
|
||||
const listener = this.cameraBarCodeReadListener
|
||||
if (listener) {
|
||||
listener.remove()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = [styles.base, this.props.style];
|
||||
const nativeProps = convertNativeProps(this.props);
|
||||
@ -169,7 +197,9 @@ export default class Camera extends Component {
|
||||
}
|
||||
|
||||
_onBarCodeRead = (data) => {
|
||||
if (this.props.onBarCodeRead) this.props.onBarCodeRead(data)
|
||||
if (this.props.onBarCodeRead) {
|
||||
this.props.onBarCodeRead(data)
|
||||
}
|
||||
};
|
||||
|
||||
capture(options) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user