mirror of
https://github.com/status-im/react-native-camera.git
synced 2025-02-23 01:08:07 +00:00
Android support.
Working: set aspect, select camera type, set flash/torch mode, take picture and save it to the file system, mediaroll as data. Not working: Video capture, set orientation. Known problem: Front camera pictures are not oriented correctly.
This commit is contained in:
parent
8e1ae7c9a4
commit
844d6aaf65
34
android/build.gradle
Executable file
34
android/build.gradle
Executable file
@ -0,0 +1,34 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "com.facebook.react:react-native:0.17.+"
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lwansbrough.ReactCamera">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lwansbrough.RCTCamera">
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
201
android/src/main/java/com/lwansbrough/RCTCamera/RCTCamera.java
Normal file
201
android/src/main/java/com/lwansbrough/RCTCamera/RCTCamera.java
Normal file
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16.
|
||||
*/
|
||||
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class RCTCamera {
|
||||
|
||||
private static final RCTCamera ourInstance = new RCTCamera();
|
||||
private final HashMap<Integer, CameraInfoWrapper> _cameraInfos;
|
||||
private final HashMap<Integer, Integer> _cameraTypeToIndex;
|
||||
private final Map<Number, Camera> _cameras;
|
||||
private int _orientation = -1;
|
||||
private int _actualDeviceOrientation = 0;
|
||||
|
||||
public static RCTCamera getInstance() {
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
public Camera acquireCameraInstance(int type) {
|
||||
if (null == _cameras.get(type) && null != _cameraTypeToIndex.get(type)) {
|
||||
try {
|
||||
Camera camera = Camera.open(_cameraTypeToIndex.get(type));
|
||||
_cameras.put(type, camera);
|
||||
adjustPreviewLayout(type);
|
||||
} catch (Exception e) {
|
||||
System.console().printf("acquireCameraInstance: %s", e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
return _cameras.get(type);
|
||||
}
|
||||
|
||||
public void releaseCameraInstance(int type) {
|
||||
if (null != _cameras.get(type)) {
|
||||
_cameras.get(type).release();
|
||||
_cameras.remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
public int getPreviewWidth(int type) {
|
||||
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||
if (null == cameraInfo) {
|
||||
return 0;
|
||||
}
|
||||
return cameraInfo.previewWidth;
|
||||
}
|
||||
|
||||
public int getPreviewHeight(int type) {
|
||||
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||
if (null == cameraInfo) {
|
||||
return 0;
|
||||
}
|
||||
return cameraInfo.previewHeight;
|
||||
}
|
||||
|
||||
public void setOrientation(int orientation) {
|
||||
if (_orientation == orientation) {
|
||||
return;
|
||||
}
|
||||
_orientation = orientation;
|
||||
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||
}
|
||||
|
||||
public void setActualDeviceOrientation(int actualDeviceOrientation) {
|
||||
_actualDeviceOrientation = actualDeviceOrientation;
|
||||
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||
adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||
}
|
||||
|
||||
public void setTorchMode(int cameraType, int torchMode) {
|
||||
Camera camera = _cameras.get(cameraType);
|
||||
if (null == camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
String value = parameters.getFlashMode();
|
||||
switch (torchMode) {
|
||||
case RCTCameraModule.RCT_CAMERA_TORCH_MODE_ON:
|
||||
value = Camera.Parameters.FLASH_MODE_TORCH;
|
||||
break;
|
||||
case RCTCameraModule.RCT_CAMERA_TORCH_MODE_OFF:
|
||||
value = Camera.Parameters.FLASH_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
List<String> flashModes = parameters.getSupportedFlashModes();
|
||||
if (flashModes != null && flashModes.contains(value)) {
|
||||
parameters.setFlashMode(value);
|
||||
camera.setParameters(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFlashMode(int cameraType, int flashMode) {
|
||||
Camera camera = _cameras.get(cameraType);
|
||||
if (null == camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
String value = parameters.getFlashMode();
|
||||
switch (flashMode) {
|
||||
case RCTCameraModule.RCT_CAMERA_FLASH_MODE_AUTO:
|
||||
value = Camera.Parameters.FLASH_MODE_AUTO;
|
||||
break;
|
||||
case RCTCameraModule.RCT_CAMERA_FLASH_MODE_ON:
|
||||
value = Camera.Parameters.FLASH_MODE_ON;
|
||||
break;
|
||||
case RCTCameraModule.RCT_CAMERA_FLASH_MODE_OFF:
|
||||
value = Camera.Parameters.FLASH_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
List<String> flashModes = parameters.getSupportedFlashModes();
|
||||
if (flashModes != null && flashModes.contains(value)) {
|
||||
parameters.setFlashMode(value);
|
||||
camera.setParameters(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustPreviewLayout(int type) {
|
||||
Camera camera = _cameras.get(type);
|
||||
if (null == camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
CameraInfoWrapper cameraInfo = _cameraInfos.get(type);
|
||||
int rotation = cameraInfo.info.orientation;
|
||||
if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
rotation = (720 - rotation - _actualDeviceOrientation * 90) % 360;
|
||||
} else {
|
||||
rotation = (rotation - _actualDeviceOrientation * 90 + 360) % 360;
|
||||
}
|
||||
cameraInfo.rotation = rotation;
|
||||
// TODO: take in account the _orientation prop
|
||||
|
||||
camera.setDisplayOrientation(cameraInfo.rotation);
|
||||
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
parameters.setRotation(cameraInfo.rotation);
|
||||
|
||||
// set preview size
|
||||
int width = parameters.getSupportedPreviewSizes().get(0).width;
|
||||
int height = parameters.getSupportedPreviewSizes().get(0).height;
|
||||
|
||||
parameters.setPreviewSize(width, height);
|
||||
try {
|
||||
camera.setParameters(parameters);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (cameraInfo.rotation == 0 || cameraInfo.rotation == 180) {
|
||||
cameraInfo.previewWidth = width;
|
||||
cameraInfo.previewHeight = height;
|
||||
} else {
|
||||
cameraInfo.previewWidth = height;
|
||||
cameraInfo.previewHeight = width;
|
||||
}
|
||||
}
|
||||
|
||||
private RCTCamera() {
|
||||
_cameras = new HashMap<>();
|
||||
_cameraInfos = new HashMap<>();
|
||||
_cameraTypeToIndex = new HashMap<>();
|
||||
|
||||
// map camera types to camera indexes and collect cameras properties
|
||||
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
|
||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
||||
Camera.getCameraInfo(i, info);
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && _cameraInfos.get(RCTCameraModule.RCT_CAMERA_TYPE_FRONT) == null) {
|
||||
_cameraInfos.put(RCTCameraModule.RCT_CAMERA_TYPE_FRONT, new CameraInfoWrapper(info));
|
||||
_cameraTypeToIndex.put(RCTCameraModule.RCT_CAMERA_TYPE_FRONT, i);
|
||||
acquireCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||
releaseCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_FRONT);
|
||||
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK && _cameraInfos.get(RCTCameraModule.RCT_CAMERA_TYPE_BACK) == null) {
|
||||
_cameraInfos.put(RCTCameraModule.RCT_CAMERA_TYPE_BACK, new CameraInfoWrapper(info));
|
||||
_cameraTypeToIndex.put(RCTCameraModule.RCT_CAMERA_TYPE_BACK, i);
|
||||
acquireCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||
releaseCameraInstance(RCTCameraModule.RCT_CAMERA_TYPE_BACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CameraInfoWrapper {
|
||||
public final Camera.CameraInfo info;
|
||||
public int rotation = 0;
|
||||
public int previewWidth = -1;
|
||||
public int previewHeight = -1;
|
||||
|
||||
public CameraInfoWrapper(Camera.CameraInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16.
|
||||
*/
|
||||
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.hardware.Camera;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import com.facebook.react.bridge.*;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RCTCameraModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RCTCameraModule";
|
||||
|
||||
public static final int RCT_CAMERA_ASPECT_FILL = 0;
|
||||
public static final int RCT_CAMERA_ASPECT_FIT = 1;
|
||||
public static final int RCT_CAMERA_ASPECT_STRETCH = 2;
|
||||
public static final int RCT_CAMERA_CAPTURE_MODE_STILL = 0;
|
||||
public static final int RCT_CAMERA_CAPTURE_MODE_VIDEO = 1;
|
||||
public static final int RCT_CAMERA_CAPTURE_TARGET_MEMORY = 0;
|
||||
public static final int RCT_CAMERA_CAPTURE_TARGET_DISK = 1;
|
||||
public static final int RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL = 2;
|
||||
public static final int RCT_CAMERA_ORIENTATION_AUTO = 0;
|
||||
public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT = 1;
|
||||
public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT = 2;
|
||||
public static final int RCT_CAMERA_ORIENTATION_PORTRAIT = 3;
|
||||
public static final int RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN = 4;
|
||||
public static final int RCT_CAMERA_TYPE_FRONT = 1;
|
||||
public static final int RCT_CAMERA_TYPE_BACK = 2;
|
||||
public static final int RCT_CAMERA_FLASH_MODE_OFF = 0;
|
||||
public static final int RCT_CAMERA_FLASH_MODE_ON = 1;
|
||||
public static final int RCT_CAMERA_FLASH_MODE_AUTO = 2;
|
||||
public static final int RCT_CAMERA_TORCH_MODE_OFF = 0;
|
||||
public static final int RCT_CAMERA_TORCH_MODE_ON = 1;
|
||||
public static final int RCT_CAMERA_TORCH_MODE_AUTO = 2;
|
||||
public static final int MEDIA_TYPE_IMAGE = 1;
|
||||
public static final int MEDIA_TYPE_VIDEO = 2;
|
||||
|
||||
private final ReactApplicationContext _reactContext;
|
||||
|
||||
public RCTCameraModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
_reactContext = reactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RCTCameraModule";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("Aspect", getAspectConstants());
|
||||
put("Type", getTypeConstants());
|
||||
put("CaptureMode", getCaptureModeConstants());
|
||||
put("CaptureTarget", getCaptureTargetConstants());
|
||||
put("Orientation", getOrientationConstants());
|
||||
put("FlashMode", getFlashModeConstants());
|
||||
put("TorchMode", getTorchModeConstants());
|
||||
}
|
||||
|
||||
private Map<String, Object> getAspectConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("stretch", RCT_CAMERA_ASPECT_STRETCH);
|
||||
put("fit", RCT_CAMERA_ASPECT_FIT);
|
||||
put("fill", RCT_CAMERA_ASPECT_FILL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getTypeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("front", RCT_CAMERA_TYPE_FRONT);
|
||||
put("back", RCT_CAMERA_TYPE_BACK);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getCaptureModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("still", RCT_CAMERA_CAPTURE_MODE_STILL);
|
||||
put("video", RCT_CAMERA_CAPTURE_MODE_VIDEO);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getCaptureTargetConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("memory", RCT_CAMERA_CAPTURE_TARGET_MEMORY);
|
||||
put("disk", RCT_CAMERA_CAPTURE_TARGET_DISK);
|
||||
put("cameraRoll", RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getOrientationConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("auto", RCT_CAMERA_ORIENTATION_AUTO);
|
||||
put("landscapeLeft", RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT);
|
||||
put("landscapeRight", RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT);
|
||||
put("portrait", RCT_CAMERA_ORIENTATION_PORTRAIT);
|
||||
put("portraitUpsideDown", RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFlashModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("off", RCT_CAMERA_FLASH_MODE_OFF);
|
||||
put("on", RCT_CAMERA_FLASH_MODE_ON);
|
||||
put("auto", RCT_CAMERA_FLASH_MODE_AUTO);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getTorchModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("off", RCT_CAMERA_TORCH_MODE_OFF);
|
||||
put("on", RCT_CAMERA_TORCH_MODE_ON);
|
||||
put("auto", RCT_CAMERA_TORCH_MODE_AUTO);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void capture(final ReadableMap options, final Callback callback) {
|
||||
Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type"));
|
||||
if (null == camera) {
|
||||
callback.invoke("No camera found.", null);
|
||||
return;
|
||||
}
|
||||
camera.takePicture(null, null, new Camera.PictureCallback() {
|
||||
@Override
|
||||
public void onPictureTaken(byte[] data, Camera camera) {
|
||||
switch (options.getInt("target")) {
|
||||
case RCT_CAMERA_CAPTURE_TARGET_MEMORY:
|
||||
String encoded = Base64.encodeToString(data, Base64.DEFAULT);
|
||||
callback.invoke(null, encoded);
|
||||
break;
|
||||
case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL:
|
||||
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, bitmapOptions);
|
||||
String url = MediaStore.Images.Media.insertImage(
|
||||
_reactContext.getContentResolver(),
|
||||
bitmap, options.getString("title"),
|
||||
options.getString("description"));
|
||||
callback.invoke(null, url);
|
||||
break;
|
||||
case RCT_CAMERA_CAPTURE_TARGET_DISK:
|
||||
File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
|
||||
if (pictureFile == null) {
|
||||
callback.invoke("Error creating media file.", null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(pictureFile);
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
callback.invoke("File not found: " + e.getMessage(), null);
|
||||
} catch (IOException e) {
|
||||
callback.invoke("Error accessing file: " + e.getMessage(), null);
|
||||
}
|
||||
callback.invoke(null, Uri.fromFile(pictureFile).toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void stopCapture(final ReadableMap options, final Callback callback) {
|
||||
// TODO: implement video capture
|
||||
}
|
||||
|
||||
private File getOutputMediaFile(int type) {
|
||||
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES), "RCTCameraModule");
|
||||
|
||||
// Create the storage directory if it does not exist
|
||||
if (!mediaStorageDir.exists()) {
|
||||
if (!mediaStorageDir.mkdirs()) {
|
||||
Log.e(TAG, "failed to create directory:" + mediaStorageDir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a media file name
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||
File mediaFile;
|
||||
if (type == MEDIA_TYPE_IMAGE) {
|
||||
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
|
||||
"IMG_" + timeStamp + ".jpg");
|
||||
} else if (type == MEDIA_TYPE_VIDEO) {
|
||||
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
|
||||
"VID_" + timeStamp + ".mp4");
|
||||
} else {
|
||||
Log.e(TAG, "Unsupported media type:" + type);
|
||||
return null;
|
||||
}
|
||||
return mediaFile;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
|
||||
public class RCTCameraPackage implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
|
||||
return Collections.<NativeModule>singletonList(new RCTCameraModule(reactApplicationContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
|
||||
//noinspection ArraysAsListWithZeroOrOneArgument
|
||||
return Collections.<ViewManager>singletonList(new RCTCameraViewManager());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16.
|
||||
*/
|
||||
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.*;
|
||||
import android.hardware.SensorManager;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public class RCTCameraView extends ViewGroup {
|
||||
private final OrientationEventListener _orientationListener;
|
||||
private final Context _context;
|
||||
private RCTCameraViewFinder _viewFinder = null;
|
||||
private int _actualDeviceOrientation = -1;
|
||||
private int _aspect = RCTCameraModule.RCT_CAMERA_ASPECT_FIT;
|
||||
private int _torchMode = -1;
|
||||
private int _flashMode = -1;
|
||||
|
||||
public RCTCameraView(Context context) {
|
||||
super(context);
|
||||
this._context = context;
|
||||
setActualDeviceOrientation(context);
|
||||
|
||||
_orientationListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
if (setActualDeviceOrientation(_context)) {
|
||||
layoutViewFinder();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (_orientationListener.canDetectOrientation()) {
|
||||
_orientationListener.enable();
|
||||
} else {
|
||||
_orientationListener.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
layoutViewFinder(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public void setAspect(int aspect) {
|
||||
this._aspect = aspect;
|
||||
layoutViewFinder();
|
||||
}
|
||||
|
||||
public void setCameraType(final int type) {
|
||||
if (null != this._viewFinder) {
|
||||
this._viewFinder.setCameraType(type);
|
||||
} else {
|
||||
_viewFinder = new RCTCameraViewFinder(_context, type);
|
||||
if (-1 != this._flashMode) {
|
||||
_viewFinder.setFlashMode(this._flashMode);
|
||||
}
|
||||
if (-1 != this._torchMode) {
|
||||
_viewFinder.setFlashMode(this._torchMode);
|
||||
}
|
||||
addView(_viewFinder);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTorchMode(int torchMode) {
|
||||
this._torchMode = torchMode;
|
||||
if (this._viewFinder != null) {
|
||||
this._viewFinder.setTorchMode(torchMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFlashMode(int flashMode) {
|
||||
this._flashMode = flashMode;
|
||||
if (this._viewFinder != null) {
|
||||
this._viewFinder.setFlashMode(flashMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOrientation(int orientation) {
|
||||
RCTCamera.getInstance().setOrientation(orientation);
|
||||
if (this._viewFinder != null) {
|
||||
layoutViewFinder();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setActualDeviceOrientation(Context context) {
|
||||
int actualDeviceOrientation = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getOrientation();
|
||||
if (_actualDeviceOrientation != actualDeviceOrientation) {
|
||||
_actualDeviceOrientation = actualDeviceOrientation;
|
||||
RCTCamera.getInstance().setActualDeviceOrientation(_actualDeviceOrientation);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void layoutViewFinder() {
|
||||
layoutViewFinder(this.getLeft(), this.getTop(), this.getRight(), this.getBottom());
|
||||
}
|
||||
|
||||
private void layoutViewFinder(int left, int top, int right, int bottom) {
|
||||
if (null == _viewFinder) {
|
||||
return;
|
||||
}
|
||||
float width = right - left;
|
||||
float height = bottom - top;
|
||||
int viewfinderWidth;
|
||||
int viewfinderHeight;
|
||||
double ratio;
|
||||
switch (this._aspect) {
|
||||
case RCTCameraModule.RCT_CAMERA_ASPECT_FIT:
|
||||
ratio = this._viewFinder.getRatio();
|
||||
if (ratio * height > width) {
|
||||
viewfinderHeight = (int) (width / ratio);
|
||||
viewfinderWidth = (int) width;
|
||||
} else {
|
||||
viewfinderWidth = (int) (ratio * height);
|
||||
viewfinderHeight = (int) height;
|
||||
}
|
||||
break;
|
||||
case RCTCameraModule.RCT_CAMERA_ASPECT_FILL:
|
||||
ratio = this._viewFinder.getRatio();
|
||||
if (ratio * height < width) {
|
||||
viewfinderHeight = (int) (width / ratio);
|
||||
viewfinderWidth = (int) width;
|
||||
} else {
|
||||
viewfinderWidth = (int) (ratio * height);
|
||||
viewfinderHeight = (int) height;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
viewfinderWidth = (int) width;
|
||||
viewfinderHeight = (int) height;
|
||||
}
|
||||
|
||||
int viewFinderPaddingX = (int) ((width - viewfinderWidth) / 2);
|
||||
int viewFinderPaddingY = (int) ((height - viewfinderHeight) / 2);
|
||||
|
||||
this._viewFinder.layout(viewFinderPaddingX, viewFinderPaddingY, viewFinderPaddingX + viewfinderWidth, viewFinderPaddingY + viewfinderHeight);
|
||||
this.postInvalidate(this.getLeft(), this.getTop(), this.getRight(), this.getBottom());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16.
|
||||
*/
|
||||
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.Camera;
|
||||
import android.view.TextureView;
|
||||
|
||||
|
||||
class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener {
|
||||
private int _cameraType;
|
||||
private SurfaceTexture _surfaceTexture;
|
||||
private boolean _isStarting;
|
||||
private boolean _isStopping;
|
||||
private Camera _camera;
|
||||
|
||||
public RCTCameraViewFinder(Context context, int type) {
|
||||
super(context);
|
||||
this.setSurfaceTextureListener(this);
|
||||
this._cameraType = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||
_surfaceTexture = surface;
|
||||
startCamera();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||
_surfaceTexture = null;
|
||||
stopCamera();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
||||
}
|
||||
|
||||
public double getRatio() {
|
||||
int width = RCTCamera.getInstance().getPreviewWidth(this._cameraType);
|
||||
int height = RCTCamera.getInstance().getPreviewHeight(this._cameraType);
|
||||
return ((float) width) / ((float) height);
|
||||
}
|
||||
|
||||
public void setCameraType(final int type) {
|
||||
if (this._cameraType == type) {
|
||||
return;
|
||||
}
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stopPreview();
|
||||
_cameraType = type;
|
||||
startPreview();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void setTorchMode(int torchMode) {
|
||||
RCTCamera.getInstance().setTorchMode(_cameraType, torchMode);
|
||||
}
|
||||
|
||||
public void setFlashMode(int flashMode) {
|
||||
RCTCamera.getInstance().setTorchMode(_cameraType, flashMode);
|
||||
}
|
||||
|
||||
private void startPreview() {
|
||||
if (_surfaceTexture != null) {
|
||||
startCamera();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPreview() {
|
||||
if (_camera != null) {
|
||||
stopCamera();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private void startCamera() {
|
||||
if (!_isStarting) {
|
||||
_isStarting = true;
|
||||
try {
|
||||
_camera = RCTCamera.getInstance().acquireCameraInstance(_cameraType);
|
||||
_camera.setPreviewTexture(_surfaceTexture);
|
||||
_camera.startPreview();
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
stopCamera();
|
||||
} finally {
|
||||
_isStarting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private void stopCamera() {
|
||||
if (!_isStopping) {
|
||||
_isStopping = true;
|
||||
try {
|
||||
if (_camera != null) {
|
||||
_camera.stopPreview();
|
||||
RCTCamera.getInstance().releaseCameraInstance(_cameraType);
|
||||
_camera = null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
_isStopping = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import com.facebook.react.uimanager.*;
|
||||
|
||||
public class RCTCameraViewManager extends SimpleViewManager<RCTCameraView> {
|
||||
private static final String REACT_CLASS = "RCTCameraView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RCTCameraView createViewInstance(ThemedReactContext context) {
|
||||
return new RCTCameraView(context);
|
||||
}
|
||||
|
||||
@ReactProp(name = "aspect")
|
||||
public void setAspect(RCTCameraView view, int aspect) {
|
||||
view.setAspect(aspect);
|
||||
}
|
||||
|
||||
@ReactProp(name = "captureMode")
|
||||
public void setCaptureMode(RCTCameraView view, int captureMode) {
|
||||
// TODO - implement video mode
|
||||
}
|
||||
|
||||
@ReactProp(name = "captureTarget")
|
||||
public void setCaptureTarget(RCTCameraView view, int captureTarget) {
|
||||
// No reason to handle this props value here since it's passed again to the RCTCameraModule capture method
|
||||
}
|
||||
|
||||
@ReactProp(name = "type")
|
||||
public void setType(RCTCameraView view, int type) {
|
||||
view.setCameraType(type);
|
||||
}
|
||||
|
||||
@ReactProp(name = "torchMode")
|
||||
public void setTorchMode(RCTCameraView view, int torchMode) {
|
||||
view.setTorchMode(torchMode);
|
||||
}
|
||||
|
||||
@ReactProp(name = "flashMode")
|
||||
public void setFlashMode(RCTCameraView view, int flashMode) {
|
||||
view.setFlashMode(flashMode);
|
||||
}
|
||||
|
||||
@ReactProp(name = "orientation")
|
||||
public void setOrientation(RCTCameraView view, int orientation) {
|
||||
view.setOrientation(orientation);
|
||||
}
|
||||
|
||||
@ReactProp(name = "captureAudio")
|
||||
public void setCaptureAudio(RCTCameraView view, boolean captureAudio) {
|
||||
// TODO - implement video mode
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
|
||||
public class ReactCamera implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Arrays.<ViewManager>asList(
|
||||
new ReactCameraManager()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package com.lwansbrough.RCTCamera;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
|
||||
public class ReactCameraManager extends SimpleViewManager<ReactCameraView> {
|
||||
|
||||
public static final String REACT_CLASS = "RCTCamera";
|
||||
|
||||
@UIProp(UIProp.Type.STRING)
|
||||
public static final String PROP_SRC = "src";
|
||||
@UIProp(UIProp.Type.NUMBER)
|
||||
public static final String PROP_BORDER_RADIUS = "borderRadius";
|
||||
@UIProp(UIProp.Type.STRING)
|
||||
public static final String PROP_RESIZE_MODE = ViewProps.RESIZE_MODE;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactCameraView createViewInstance(ThemedReactContext context) {
|
||||
return new ReactCameraView(context, Fresco.newDraweeControllerBuilder(), mCallerContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateView(final ReactImageView view, final CatalystStylesDiffMap props) {
|
||||
super.updateView(view, props);
|
||||
|
||||
if (props.hasKey(PROP_RESIZE_MODE)) {
|
||||
view.setScaleType(
|
||||
ImageResizeMode.toScaleType(props.getString(PROP_RESIZE_MODE)));
|
||||
}
|
||||
if (props.hasKey(PROP_SRC)) {
|
||||
view.setSource(props.getString(PROP_SRC));
|
||||
}
|
||||
if (props.hasKey(PROP_BORDER_RADIUS)) {
|
||||
view.setBorderRadius(props.getFloat(PROP_BORDER_RADIUS, 0.0f));
|
||||
}
|
||||
view.maybeUpdateView();
|
||||
}
|
||||
}
|
213
index.android.js
213
index.android.js
@ -1,12 +1,207 @@
|
||||
var { requireNativeComponent, PropTypes } = require('react-native');
|
||||
var React = require('react-native');
|
||||
var { View, StyleSheet, requireNativeComponent, PropTypes, NativeModules, DeviceEventEmitter } = React;
|
||||
|
||||
var iface = {
|
||||
name: 'ImageView',
|
||||
propTypes: {
|
||||
src: PropTypes.string,
|
||||
borderRadius: PropTypes.number,
|
||||
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
|
||||
},
|
||||
var CAMERA_REF = 'camera';
|
||||
|
||||
var constants = {
|
||||
Aspect: NativeModules.CameraModule.Aspect,
|
||||
BarCodeType: NativeModules.CameraModule.BarCodeType,
|
||||
Type: NativeModules.CameraModule.Type,
|
||||
CaptureMode: NativeModules.CameraModule.CaptureMode,
|
||||
CaptureTarget: NativeModules.CameraModule.CaptureTarget,
|
||||
Orientation: NativeModules.CameraModule.Orientation,
|
||||
FlashMode: NativeModules.CameraModule.FlashMode,
|
||||
TorchMode: NativeModules.CameraModule.TorchMode
|
||||
};
|
||||
|
||||
module.exports = requireNativeComponent('RCTCamera', iface);
|
||||
var Camera = React.createClass({
|
||||
propTypes: {
|
||||
aspect: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
captureAudio: PropTypes.bool,
|
||||
captureMode: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
captureTarget: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
type: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
orientation: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
flashMode: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
torchMode: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
defaultOnFocusComponent: PropTypes.bool,
|
||||
onFocusChanged: PropTypes.func,
|
||||
onZoomChanged: PropTypes.func,
|
||||
...View.propTypes
|
||||
},
|
||||
|
||||
setNativeProps(props) {
|
||||
this.refs[CAMERA_REF].setNativeProps(props);
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
aspect: constants.Aspect.fill,
|
||||
type: constants.Type.back,
|
||||
orientation: constants.Orientation.auto,
|
||||
captureAudio: true,
|
||||
captureMode: constants.CaptureMode.still,
|
||||
captureTarget: constants.CaptureTarget.cameraRoll,
|
||||
flashMode: constants.FlashMode.off,
|
||||
torchMode: constants.TorchMode.off
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
isAuthorized: false,
|
||||
isRecording: false
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
//// TODO: handle properly Android 6 new permissions style
|
||||
this.state.isAuthorized = true;
|
||||
this.setState(this.state);
|
||||
this.cameraBarCodeReadListener = DeviceEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.cameraBarCodeReadListener.remove();
|
||||
|
||||
if (this.state.isRecording) {
|
||||
this.stopCapture();
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
var style = [styles.base, this.props.style];
|
||||
|
||||
var aspect = this.props.aspect,
|
||||
type = this.props.type,
|
||||
orientation = this.props.orientation,
|
||||
flashMode = this.props.flashMode,
|
||||
torchMode = this.props.torchMode;
|
||||
|
||||
var legacyProps = {
|
||||
aspect: {
|
||||
Fill: 'fill',
|
||||
Fit: 'fit',
|
||||
Stretch: 'stretch'
|
||||
},
|
||||
orientation: {
|
||||
LandscapeLeft: 'landscapeLeft',
|
||||
LandscapeRight: 'landscapeRight',
|
||||
Portrait: 'portrait',
|
||||
PortraitUpsideDown: 'portraitUpsideDown'
|
||||
},
|
||||
type: {
|
||||
Front: 'front',
|
||||
Back: 'back'
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof aspect === 'string') {
|
||||
aspect = constants.Aspect[aspect];
|
||||
}
|
||||
|
||||
if (typeof flashMode === 'string') {
|
||||
flashMode = constants.FlashMode[flashMode];
|
||||
}
|
||||
|
||||
if (typeof orientation === 'string') {
|
||||
orientation = constants.Orientation[orientation];
|
||||
}
|
||||
|
||||
if (typeof torchMode === 'string') {
|
||||
torchMode = constants.TorchMode[torchMode];
|
||||
}
|
||||
|
||||
if (typeof type === 'string') {
|
||||
type = constants.Type[type];
|
||||
}
|
||||
|
||||
var nativeProps = Object.assign({}, this.props, {
|
||||
style,
|
||||
aspect: aspect,
|
||||
type: type,
|
||||
orientation: orientation,
|
||||
flashMode: flashMode,
|
||||
torchMode: torchMode
|
||||
});
|
||||
|
||||
return <RCTCameraView ref={CAMERA_REF} {... nativeProps} />;
|
||||
},
|
||||
|
||||
_onBarCodeRead(e) {
|
||||
this.props.onBarCodeRead && this.props.onBarCodeRead(e);
|
||||
},
|
||||
|
||||
capture(options, cb) {
|
||||
|
||||
if (arguments.length == 1) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options = Object.assign({}, {
|
||||
audio: this.props.captureAudio,
|
||||
mode: this.props.captureMode,
|
||||
target: this.props.captureTarget,
|
||||
type: this.props.type
|
||||
}, options);
|
||||
|
||||
if (typeof options.mode === 'string') {
|
||||
options.mode = constants.CaptureMode[options.mode];
|
||||
}
|
||||
|
||||
if (options.mode === constants.CaptureMode.video) {
|
||||
options.totalSeconds = (options.totalSeconds > -1 ? options.totalSeconds : -1);
|
||||
options.preferredTimeScale = options.preferredTimeScale || 30;
|
||||
this.setState({isRecording: true});
|
||||
}
|
||||
|
||||
if (typeof options.target === 'string') {
|
||||
options.target = constants.CaptureTarget[options.target];
|
||||
}
|
||||
|
||||
if (typeof options.type === 'string') {
|
||||
options.type = constants.Type[options.type];
|
||||
}
|
||||
|
||||
NativeModules.CameraModule.capture(options, cb);
|
||||
},
|
||||
|
||||
stopCapture() {
|
||||
if (this.state.isRecording) {
|
||||
NativeModules.CameraManager.stopCapture();
|
||||
this.setState({ isRecording: false });
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var RCTCameraView = requireNativeComponent('RCTCameraView', Camera);
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
base: {},
|
||||
});
|
||||
|
||||
Camera.constants = constants;
|
||||
module.exports = Camera;
|
||||
|
Loading…
x
Reference in New Issue
Block a user