/** * Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16. */ package com.lwansbrough.RCTCamera; import android.hardware.Camera; import android.media.CamcorderProfile; import android.util.Log; import java.util.HashMap; import java.util.List; import java.util.Map; import java.lang.Math; public class RCTCamera { private static RCTCamera ourInstance; private final HashMap<Integer, CameraInfoWrapper> _cameraInfos; private final HashMap<Integer, Integer> _cameraTypeToIndex; private final Map<Number, Camera> _cameras; private static final Resolution RESOLUTION_480P = new Resolution(853, 480); // 480p shoots for a 16:9 HD aspect ratio, but can otherwise fall back/down to any other supported camera sizes, such as 800x480 or 720x480, if (any) present. See getSupportedPictureSizes/getSupportedVideoSizes below. private static final Resolution RESOLUTION_720P = new Resolution(1280, 720); private static final Resolution RESOLUTION_1080P = new Resolution(1920, 1080); private boolean _barcodeScannerEnabled = false; private List<String> _barCodeTypes = null; private int _orientation = -1; private int _actualDeviceOrientation = 0; private int _adjustedDeviceOrientation = 0; public static RCTCamera getInstance() { return ourInstance; } public static void createInstance(int deviceOrientation) { ourInstance = new RCTCamera(deviceOrientation); } public synchronized 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) { Log.e("RCTCamera", "acquireCameraInstance failed", e); } } return _cameras.get(type); } public void releaseCameraInstance(int type) { // Release seems async and creates race conditions. Remove from map first before releasing. Camera releasingCamera = _cameras.get(type); if (null != releasingCamera) { _cameras.remove(type); releasingCamera.release(); } } 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 Camera.Size getBestSize(List<Camera.Size> supportedSizes, int maxWidth, int maxHeight) { Camera.Size bestSize = null; for (Camera.Size size : supportedSizes) { if (size.width > maxWidth || size.height > maxHeight) { continue; } if (bestSize == null) { bestSize = size; continue; } int resultArea = bestSize.width * bestSize.height; int newArea = size.width * size.height; if (newArea > resultArea) { bestSize = size; } } return bestSize; } private Camera.Size getSmallestSize(List<Camera.Size> supportedSizes) { Camera.Size smallestSize = null; for (Camera.Size size : supportedSizes) { if (smallestSize == null) { smallestSize = size; continue; } int resultArea = smallestSize.width * smallestSize.height; int newArea = size.width * size.height; if (newArea < resultArea) { smallestSize = size; } } return smallestSize; } private Camera.Size getClosestSize(List<Camera.Size> supportedSizes, int matchWidth, int matchHeight) { Camera.Size closestSize = null; for (Camera.Size size : supportedSizes) { if (closestSize == null) { closestSize = size; continue; } int currentDelta = Math.abs(closestSize.width - matchWidth) * Math.abs(closestSize.height - matchHeight); int newDelta = Math.abs(size.width - matchWidth) * Math.abs(size.height - matchHeight); if (newDelta < currentDelta) { closestSize = size; } } return closestSize; } protected List<Camera.Size> getSupportedVideoSizes(Camera camera) { Camera.Parameters params = camera.getParameters(); // defer to preview instead of params.getSupportedVideoSizes() http://bit.ly/1rxOsq0 // but prefer SupportedVideoSizes! List<Camera.Size> sizes = params.getSupportedVideoSizes(); if (sizes != null) { return sizes; } // Video sizes may be null, which indicates that all the supported // preview sizes are supported for video recording. return params.getSupportedPreviewSizes(); } public int getOrientation() { return _orientation; } public void setOrientation(int orientation) { if (_orientation == orientation) { return; } _orientation = orientation; adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT); 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; } public void setAdjustedDeviceOrientation(int orientation) { _adjustedDeviceOrientation = orientation; } public int getAdjustedDeviceOrientation() { return _adjustedDeviceOrientation; } public void setActualDeviceOrientation(int actualDeviceOrientation) { _actualDeviceOrientation = actualDeviceOrientation; adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_FRONT); adjustPreviewLayout(RCTCameraModule.RCT_CAMERA_TYPE_BACK); } public void setCaptureMode(final int cameraType, final int captureMode) { Camera camera = _cameras.get(cameraType); if (camera == null) { return; } // Set (video) recording hint based on camera type. For video recording, setting // this hint can help reduce the time it takes to start recording. Camera.Parameters parameters = camera.getParameters(); parameters.setRecordingHint(captureMode == RCTCameraModule.RCT_CAMERA_CAPTURE_MODE_VIDEO); camera.setParameters(parameters); } public void setCaptureQuality(int cameraType, String captureQuality) { Camera camera = this.acquireCameraInstance(cameraType); if (camera == null) { return; } Camera.Parameters parameters = camera.getParameters(); Camera.Size pictureSize = null; List<Camera.Size> supportedSizes = parameters.getSupportedPictureSizes(); switch (captureQuality) { case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_LOW: pictureSize = getSmallestSize(supportedSizes); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_MEDIUM: pictureSize = supportedSizes.get(supportedSizes.size() / 2); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_HIGH: pictureSize = getBestSize(parameters.getSupportedPictureSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_PREVIEW: Camera.Size optimalPreviewSize = getBestSize(parameters.getSupportedPreviewSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE); pictureSize = getClosestSize(parameters.getSupportedPictureSizes(), optimalPreviewSize.width, optimalPreviewSize.height); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_480P: pictureSize = getBestSize(supportedSizes, RESOLUTION_480P.width, RESOLUTION_480P.height); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_720P: pictureSize = getBestSize(supportedSizes, RESOLUTION_720P.width, RESOLUTION_720P.height); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_1080P: pictureSize = getBestSize(supportedSizes, RESOLUTION_1080P.width, RESOLUTION_1080P.height); break; } if (pictureSize != null) { parameters.setPictureSize(pictureSize.width, pictureSize.height); camera.setParameters(parameters); } } public CamcorderProfile setCaptureVideoQuality(int cameraType, String captureQuality) { Camera camera = this.acquireCameraInstance(cameraType); if (camera == null) { return null; } Camera.Size videoSize = null; List<Camera.Size> supportedSizes = getSupportedVideoSizes(camera); CamcorderProfile cm = null; switch (captureQuality) { case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_LOW: videoSize = getSmallestSize(supportedSizes); cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_480P); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_MEDIUM: videoSize = supportedSizes.get(supportedSizes.size() / 2); cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_720P); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_HIGH: videoSize = getBestSize(supportedSizes, Integer.MAX_VALUE, Integer.MAX_VALUE); cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_HIGH); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_480P: videoSize = getBestSize(supportedSizes, RESOLUTION_480P.width, RESOLUTION_480P.height); cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_480P); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_720P: videoSize = getBestSize(supportedSizes, RESOLUTION_720P.width, RESOLUTION_720P.height); cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_720P); break; case RCTCameraModule.RCT_CAMERA_CAPTURE_QUALITY_1080P: videoSize = getBestSize(supportedSizes, RESOLUTION_1080P.width, RESOLUTION_1080P.height); cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_1080P); break; } if (cm == null){ return null; } if (videoSize != null) { cm.videoFrameHeight = videoSize.height; cm.videoFrameWidth = videoSize.width; } return cm; } public void setTorchMode(int cameraType, int torchMode) { Camera camera = this.acquireCameraInstance(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 = this.acquireCameraInstance(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); } } public void adjustCameraRotationToDeviceOrientation(int type, int deviceOrientation) { Camera camera = _cameras.get(type); if (null == camera) { return; } CameraInfoWrapper cameraInfo = _cameraInfos.get(type); int rotation; int orientation = cameraInfo.info.orientation; if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { rotation = (orientation + deviceOrientation * 90) % 360; } else { rotation = (orientation - deviceOrientation * 90 + 360) % 360; } cameraInfo.rotation = rotation; Camera.Parameters parameters = camera.getParameters(); parameters.setRotation(cameraInfo.rotation); try { camera.setParameters(parameters); } catch (Exception e) { e.printStackTrace(); } } public void adjustPreviewLayout(int type) { Camera camera = _cameras.get(type); if (null == camera) { return; } CameraInfoWrapper cameraInfo = _cameraInfos.get(type); int displayRotation; int rotation; int orientation = cameraInfo.info.orientation; if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { rotation = (orientation + _actualDeviceOrientation * 90) % 360; displayRotation = (720 - orientation - _actualDeviceOrientation * 90) % 360; } else { rotation = (orientation - _actualDeviceOrientation * 90 + 360) % 360; displayRotation = rotation; } cameraInfo.rotation = rotation; // TODO: take in account the _orientation prop setAdjustedDeviceOrientation(rotation); camera.setDisplayOrientation(displayRotation); Camera.Parameters parameters = camera.getParameters(); parameters.setRotation(cameraInfo.rotation); // set preview size // defaults to highest resolution available Camera.Size optimalPreviewSize = getBestSize(parameters.getSupportedPreviewSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE); int width = optimalPreviewSize.width; int height = optimalPreviewSize.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(int deviceOrientation) { _cameras = new HashMap<>(); _cameraInfos = new HashMap<>(); _cameraTypeToIndex = new HashMap<>(); _actualDeviceOrientation = deviceOrientation; // 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; } } private static class Resolution { public int width; public int height; public Resolution(final int width, final int height) { this.width = width; this.height = height; } } }