From 109833294c5dc50edd6c833e28eb27431ec1f750 Mon Sep 17 00:00:00 2001 From: Jarle Fagerheim Date: Mon, 26 Mar 2018 10:24:10 +0200 Subject: [PATCH 01/23] support CIF video quality on iOS --- docs/RNCamera.md | 3 +++ ios/RN/RNCameraManager.h | 1 + ios/RN/RNCameraManager.m | 1 + ios/RN/RNCameraUtils.m | 2 ++ 4 files changed, 7 insertions(+) diff --git a/docs/RNCamera.md b/docs/RNCamera.md index 78f882715a..646f17e84e 100644 --- a/docs/RNCamera.md +++ b/docs/RNCamera.md @@ -291,6 +291,9 @@ The promise will be fulfilled with an object with some of the following properti - `RNCamera.Constants.VideoQuality.4:3`. - `ios` Specifies capture settings suitable for VGA quality (640x480 pixel) video output. (Same as RNCamera.Constants.VideoQuality.480p). - `android` Quality level corresponding to the 480p (720 x 480) resolution but with video frame width set to 640. + - `RNCamera.Constants.VideoQuality.288p`. + - `ios` Specifies capture settings suitable for CIF quality (352x288 pixel) video output. + - `android` Not supported. If nothing is passed the device's highest camera quality will be used as default. - `iOS` `codec`. This option specifies the codec of the output video. Setting the codec is only supported on `iOS >= 10`. The possible values are: diff --git a/ios/RN/RNCameraManager.h b/ios/RN/RNCameraManager.h index 52bc556f25..f900f3f996 100644 --- a/ios/RN/RNCameraManager.h +++ b/ios/RN/RNCameraManager.h @@ -44,6 +44,7 @@ typedef NS_ENUM(NSInteger, RNCameraVideoResolution) { RNCameraVideo1080p = 1, RNCameraVideo720p = 2, RNCameraVideo4x3 = 3, + RNCameraVideo288p = 4, }; @interface RNCameraManager : RCTViewManager diff --git a/ios/RN/RNCameraManager.m b/ios/RN/RNCameraManager.m index fc4b7f07c8..0e067c0c83 100644 --- a/ios/RN/RNCameraManager.m +++ b/ios/RN/RNCameraManager.m @@ -54,6 +54,7 @@ - (NSDictionary *)constantsToExport @"720p": @(RNCameraVideo720p), @"480p": @(RNCameraVideo4x3), @"4:3": @(RNCameraVideo4x3), + @"288p": @(RNCameraVideo288p), }, @"VideoCodec": [[self class] validCodecTypes], @"BarCodeType" : [[self class] validBarCodeTypes], diff --git a/ios/RN/RNCameraUtils.m b/ios/RN/RNCameraUtils.m index 64573138a6..157c048397 100644 --- a/ios/RN/RNCameraUtils.m +++ b/ios/RN/RNCameraUtils.m @@ -87,6 +87,8 @@ + (NSString *)captureSessionPresetForVideoResolution:(RNCameraVideoResolution)re return AVCaptureSessionPreset1280x720; case RNCameraVideo4x3: return AVCaptureSessionPreset640x480; + case RNCameraVideo288p: + return AVCaptureSessionPreset352x288; default: return AVCaptureSessionPresetHigh; } From d11ed31917c26df151b4fb46ab166d2921a9ac99 Mon Sep 17 00:00:00 2001 From: Joao Fidelis Date: Sun, 15 Apr 2018 22:06:18 -0300 Subject: [PATCH 02/23] chore(cameraview): integrate google's cameraview directly on rncamera? --- android/build.gradle | 5 +- .../android/cameraview/AspectRatio.java | 189 +++ .../google/android/cameraview/Camera1.java | 798 +++++++++++ .../google/android/cameraview/Camera2.java | 1208 +++++++++++++++++ .../android/cameraview/Camera2Api23.java | 46 + .../google/android/cameraview/CameraView.java | 687 ++++++++++ .../android/cameraview/CameraViewImpl.java | 114 ++ .../google/android/cameraview/Constants.java | 42 + .../DisplayOrientationDetector.java | 96 ++ .../android/cameraview/PreviewImpl.java | 87 ++ .../com/google/android/cameraview/Size.java | 79 ++ .../google/android/cameraview/SizeMap.java | 82 ++ .../cameraview/SurfaceViewPreview.java | 88 ++ .../cameraview/TextureViewPreview.java | 146 ++ .../src/main/res/layout-v14/texture_view.xml | 23 + android/src/main/res/layout/surface_view.xml | 23 + android/src/main/res/values/attrs.xml | 58 + android/src/main/res/values/public.xml | 21 + android/src/main/res/values/styles.xml | 24 + 19 files changed, 3814 insertions(+), 2 deletions(-) create mode 100644 android/src/main/java/com/google/android/cameraview/AspectRatio.java create mode 100644 android/src/main/java/com/google/android/cameraview/Camera1.java create mode 100644 android/src/main/java/com/google/android/cameraview/Camera2.java create mode 100644 android/src/main/java/com/google/android/cameraview/Camera2Api23.java create mode 100644 android/src/main/java/com/google/android/cameraview/CameraView.java create mode 100644 android/src/main/java/com/google/android/cameraview/CameraViewImpl.java create mode 100644 android/src/main/java/com/google/android/cameraview/Constants.java create mode 100644 android/src/main/java/com/google/android/cameraview/DisplayOrientationDetector.java create mode 100644 android/src/main/java/com/google/android/cameraview/PreviewImpl.java create mode 100644 android/src/main/java/com/google/android/cameraview/Size.java create mode 100644 android/src/main/java/com/google/android/cameraview/SizeMap.java create mode 100644 android/src/main/java/com/google/android/cameraview/SurfaceViewPreview.java create mode 100644 android/src/main/java/com/google/android/cameraview/TextureViewPreview.java create mode 100644 android/src/main/res/layout-v14/texture_view.xml create mode 100644 android/src/main/res/layout/surface_view.xml create mode 100644 android/src/main/res/values/attrs.xml create mode 100644 android/src/main/res/values/public.xml create mode 100644 android/src/main/res/values/styles.xml diff --git a/android/build.gradle b/android/build.gradle index 31741db41c..6a7219c901 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,7 @@ buildscript { repositories { jcenter() + google() maven { url 'https://maven.google.com' } @@ -57,6 +58,6 @@ dependencies { implementation "com.drewnoakes:metadata-extractor:2.9.1" implementation "com.google.android.gms:play-services-vision:$googlePlayServicesVersion" implementation "com.android.support:exifinterface:$supportLibVersion" - - implementation 'com.github.react-native-community:cameraview:eb9f726215bbbfcc03a54db92e480d4d02e0c001' + implementation "com.android.support:support-annotations:$supportLibVersion" + implementation "com.android.support:support-v4:$supportLibVersion" } diff --git a/android/src/main/java/com/google/android/cameraview/AspectRatio.java b/android/src/main/java/com/google/android/cameraview/AspectRatio.java new file mode 100644 index 0000000000..d7f841d00a --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/AspectRatio.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v4.util.SparseArrayCompat; + +/** + * Immutable class for describing proportional relationship between width and height. + */ +public class AspectRatio implements Comparable, Parcelable { + + private final static SparseArrayCompat> sCache + = new SparseArrayCompat<>(16); + + private final int mX; + private final int mY; + + /** + * Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values. + * The values {@code x} and {@code} will be reduced by their greatest common divider. + * + * @param x The width + * @param y The height + * @return An instance of {@link AspectRatio} + */ + public static AspectRatio of(int x, int y) { + int gcd = gcd(x, y); + x /= gcd; + y /= gcd; + SparseArrayCompat arrayX = sCache.get(x); + if (arrayX == null) { + AspectRatio ratio = new AspectRatio(x, y); + arrayX = new SparseArrayCompat<>(); + arrayX.put(y, ratio); + sCache.put(x, arrayX); + return ratio; + } else { + AspectRatio ratio = arrayX.get(y); + if (ratio == null) { + ratio = new AspectRatio(x, y); + arrayX.put(y, ratio); + } + return ratio; + } + } + + /** + * Parse an {@link AspectRatio} from a {@link String} formatted like "4:3". + * + * @param s The string representation of the aspect ratio + * @return The aspect ratio + * @throws IllegalArgumentException when the format is incorrect. + */ + public static AspectRatio parse(String s) { + int position = s.indexOf(':'); + if (position == -1) { + throw new IllegalArgumentException("Malformed aspect ratio: " + s); + } + try { + int x = Integer.parseInt(s.substring(0, position)); + int y = Integer.parseInt(s.substring(position + 1)); + return AspectRatio.of(x, y); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Malformed aspect ratio: " + s, e); + } + } + + private AspectRatio(int x, int y) { + mX = x; + mY = y; + } + + public int getX() { + return mX; + } + + public int getY() { + return mY; + } + + public boolean matches(Size size) { + int gcd = gcd(size.getWidth(), size.getHeight()); + int x = size.getWidth() / gcd; + int y = size.getHeight() / gcd; + return mX == x && mY == y; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (this == o) { + return true; + } + if (o instanceof AspectRatio) { + AspectRatio ratio = (AspectRatio) o; + return mX == ratio.mX && mY == ratio.mY; + } + return false; + } + + @Override + public String toString() { + return mX + ":" + mY; + } + + public float toFloat() { + return (float) mX / mY; + } + + @Override + public int hashCode() { + // assuming most sizes are <2^16, doing a rotate will give us perfect hashing + return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2))); + } + + @Override + public int compareTo(@NonNull AspectRatio another) { + if (equals(another)) { + return 0; + } else if (toFloat() - another.toFloat() > 0) { + return 1; + } + return -1; + } + + /** + * @return The inverse of this {@link AspectRatio}. + */ + public AspectRatio inverse() { + //noinspection SuspiciousNameCombination + return AspectRatio.of(mY, mX); + } + + private static int gcd(int a, int b) { + while (b != 0) { + int c = b; + b = a % b; + a = c; + } + return a; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mX); + dest.writeInt(mY); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + + @Override + public AspectRatio createFromParcel(Parcel source) { + int x = source.readInt(); + int y = source.readInt(); + return AspectRatio.of(x, y); + } + + @Override + public AspectRatio[] newArray(int size) { + return new AspectRatio[size]; + } + }; + +} diff --git a/android/src/main/java/com/google/android/cameraview/Camera1.java b/android/src/main/java/com/google/android/cameraview/Camera1.java new file mode 100644 index 0000000000..5ccb3fbd1b --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/Camera1.java @@ -0,0 +1,798 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.annotation.SuppressLint; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.media.CamcorderProfile; +import android.media.MediaRecorder; +import android.os.Build; +import android.support.v4.util.SparseArrayCompat; +import android.view.SurfaceHolder; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.concurrent.atomic.AtomicBoolean; + + +@SuppressWarnings("deprecation") +class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener, + MediaRecorder.OnErrorListener, Camera.PreviewCallback { + + private static final int INVALID_CAMERA_ID = -1; + + private static final SparseArrayCompat FLASH_MODES = new SparseArrayCompat<>(); + + static { + FLASH_MODES.put(Constants.FLASH_OFF, Camera.Parameters.FLASH_MODE_OFF); + FLASH_MODES.put(Constants.FLASH_ON, Camera.Parameters.FLASH_MODE_ON); + FLASH_MODES.put(Constants.FLASH_TORCH, Camera.Parameters.FLASH_MODE_TORCH); + FLASH_MODES.put(Constants.FLASH_AUTO, Camera.Parameters.FLASH_MODE_AUTO); + FLASH_MODES.put(Constants.FLASH_RED_EYE, Camera.Parameters.FLASH_MODE_RED_EYE); + } + + private static final SparseArrayCompat WB_MODES = new SparseArrayCompat<>(); + + static { + WB_MODES.put(Constants.WB_AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); + WB_MODES.put(Constants.WB_CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); + WB_MODES.put(Constants.WB_SUNNY, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); + WB_MODES.put(Constants.WB_SHADOW, Camera.Parameters.WHITE_BALANCE_SHADE); + WB_MODES.put(Constants.WB_FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); + WB_MODES.put(Constants.WB_INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); + } + + private int mCameraId; + + private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false); + + Camera mCamera; + + private Camera.Parameters mCameraParameters; + + private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo(); + + private MediaRecorder mMediaRecorder; + + private String mVideoPath; + + private boolean mIsRecording; + + private final SizeMap mPreviewSizes = new SizeMap(); + + private final SizeMap mPictureSizes = new SizeMap(); + + private AspectRatio mAspectRatio; + + private boolean mShowingPreview; + + private boolean mAutoFocus; + + private int mFacing; + + private int mFlash; + + private int mDisplayOrientation; + + private float mZoom; + + private int mWhiteBalance; + + private boolean mIsScanning; + + private SurfaceTexture mPreviewTexture; + + Camera1(Callback callback, PreviewImpl preview) { + super(callback, preview); + preview.setCallback(new PreviewImpl.Callback() { + @Override + public void onSurfaceChanged() { + if (mCamera != null) { + setUpPreview(); + adjustCameraParameters(); + } + } + + @Override + public void onSurfaceDestroyed() { + stop(); + } + }); + } + + @Override + boolean start() { + chooseCamera(); + if (!openCamera()) { + mCallback.onMountError(); + // returning false will result in invoking this method again + return true; + } + if (mPreview.isReady()) { + setUpPreview(); + } + mShowingPreview = true; + startCameraPreview(); + return true; + } + + @Override + void stop() { + if (mCamera != null) { + mCamera.stopPreview(); + mCamera.setPreviewCallback(null); + } + mShowingPreview = false; + if (mMediaRecorder != null) { + mMediaRecorder.stop(); + mMediaRecorder.release(); + mMediaRecorder = null; + + if (mIsRecording) { + mCallback.onVideoRecorded(mVideoPath); + mIsRecording = false; + } + } + releaseCamera(); + } + + // Suppresses Camera#setPreviewTexture + @SuppressLint("NewApi") + void setUpPreview() { + try { + if (mPreviewTexture != null) { + mCamera.setPreviewTexture(mPreviewTexture); + } else if (mPreview.getOutputClass() == SurfaceHolder.class) { + final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14; + if (needsToStopPreview) { + mCamera.stopPreview(); + } + mCamera.setPreviewDisplay(mPreview.getSurfaceHolder()); + if (needsToStopPreview) { + startCameraPreview(); + } + } else { + mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void startCameraPreview() { + mCamera.startPreview(); + if (mIsScanning) { + mCamera.setPreviewCallback(this); + } + } + + @Override + boolean isCameraOpened() { + return mCamera != null; + } + + @Override + void setFacing(int facing) { + if (mFacing == facing) { + return; + } + mFacing = facing; + if (isCameraOpened()) { + stop(); + start(); + } + } + + @Override + int getFacing() { + return mFacing; + } + + @Override + Set getSupportedAspectRatios() { + SizeMap idealAspectRatios = mPreviewSizes; + for (AspectRatio aspectRatio : idealAspectRatios.ratios()) { + if (mPictureSizes.sizes(aspectRatio) == null) { + idealAspectRatios.remove(aspectRatio); + } + } + return idealAspectRatios.ratios(); + } + + @Override + boolean setAspectRatio(AspectRatio ratio) { + if (mAspectRatio == null || !isCameraOpened()) { + // Handle this later when camera is opened + mAspectRatio = ratio; + return true; + } else if (!mAspectRatio.equals(ratio)) { + final Set sizes = mPreviewSizes.sizes(ratio); + if (sizes == null) { + throw new UnsupportedOperationException(ratio + " is not supported"); + } else { + mAspectRatio = ratio; + adjustCameraParameters(); + return true; + } + } + return false; + } + + @Override + AspectRatio getAspectRatio() { + return mAspectRatio; + } + + @Override + void setAutoFocus(boolean autoFocus) { + if (mAutoFocus == autoFocus) { + return; + } + if (setAutoFocusInternal(autoFocus)) { + mCamera.setParameters(mCameraParameters); + } + } + + @Override + boolean getAutoFocus() { + if (!isCameraOpened()) { + return mAutoFocus; + } + String focusMode = mCameraParameters.getFocusMode(); + return focusMode != null && focusMode.contains("continuous"); + } + + @Override + void setFlash(int flash) { + if (flash == mFlash) { + return; + } + if (setFlashInternal(flash)) { + mCamera.setParameters(mCameraParameters); + } + } + + @Override + int getFlash() { + return mFlash; + } + + @Override + public void setFocusDepth(float value) { + // not supported for Camera1 + } + + @Override + float getFocusDepth() { + return 0; + } + + @Override + void setZoom(float zoom) { + if (zoom == mZoom) { + return; + } + if (setZoomInternal(zoom)) { + mCamera.setParameters(mCameraParameters); + } + } + + @Override + float getZoom() { + return mZoom; + } + + @Override + public void setWhiteBalance(int whiteBalance) { + if (whiteBalance == mWhiteBalance) { + return; + } + if (setWhiteBalanceInternal(whiteBalance)) { + mCamera.setParameters(mCameraParameters); + } + } + + @Override + public int getWhiteBalance() { + return mWhiteBalance; + } + + @Override + void setScanning(boolean isScanning) { + if (isScanning == mIsScanning) { + return; + } + setScanningInternal(isScanning); + } + + @Override + boolean getScanning() { + return mIsScanning; + } + + @Override + void takePicture() { + if (!isCameraOpened()) { + throw new IllegalStateException( + "Camera is not ready. Call start() before takePicture()."); + } + if (getAutoFocus()) { + mCamera.cancelAutoFocus(); + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + takePictureInternal(); + } + }); + } else { + takePictureInternal(); + } + } + + void takePictureInternal() { + if (!isPictureCaptureInProgress.getAndSet(true)) { + mCamera.takePicture(null, null, null, new Camera.PictureCallback() { + @Override + public void onPictureTaken(byte[] data, Camera camera) { + isPictureCaptureInProgress.set(false); + camera.cancelAutoFocus(); + camera.startPreview(); + if (mIsScanning) { + camera.setPreviewCallback(Camera1.this); + } + mCallback.onPictureTaken(data); + } + }); + } + } + + @Override + boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile) { + if (!mIsRecording) { + setUpMediaRecorder(path, maxDuration, maxFileSize, recordAudio, profile); + try { + mMediaRecorder.prepare(); + mMediaRecorder.start(); + mIsRecording = true; + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + return false; + } + + @Override + void stopRecording() { + if (mIsRecording) { + stopMediaRecorder(); + if (mCamera != null) { + mCamera.lock(); + } + } + } + + @Override + void setDisplayOrientation(int displayOrientation) { + if (mDisplayOrientation == displayOrientation) { + return; + } + mDisplayOrientation = displayOrientation; + if (isCameraOpened()) { + mCameraParameters.setRotation(calcCameraRotation(displayOrientation)); + mCamera.setParameters(mCameraParameters); + final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14; + if (needsToStopPreview) { + mCamera.stopPreview(); + } + mCamera.setDisplayOrientation(calcDisplayOrientation(displayOrientation)); + if (needsToStopPreview) { + startCameraPreview(); + } + } + } + + @Override + public void setPreviewTexture(SurfaceTexture surfaceTexture) { + try { + if (mCamera == null) { + mPreviewTexture = surfaceTexture; + return; + } + + mCamera.stopPreview(); + + if (surfaceTexture == null) { + mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture()); + } else { + mCamera.setPreviewTexture(surfaceTexture); + } + + mPreviewTexture = surfaceTexture; + startCameraPreview(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Size getPreviewSize() { + Camera.Size cameraSize = mCameraParameters.getPreviewSize(); + return new Size(cameraSize.width, cameraSize.height); + } + + /** + * This rewrites {@link #mCameraId} and {@link #mCameraInfo}. + */ + private void chooseCamera() { + for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) { + Camera.getCameraInfo(i, mCameraInfo); + if (mCameraInfo.facing == mFacing) { + mCameraId = i; + return; + } + } + mCameraId = INVALID_CAMERA_ID; + } + + private boolean openCamera() { + if (mCamera != null) { + releaseCamera(); + } + try { + mCamera = Camera.open(mCameraId); + mCameraParameters = mCamera.getParameters(); + // Supported preview sizes + mPreviewSizes.clear(); + for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) { + mPreviewSizes.add(new Size(size.width, size.height)); + } + // Supported picture sizes; + mPictureSizes.clear(); + for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) { + mPictureSizes.add(new Size(size.width, size.height)); + } + // AspectRatio + if (mAspectRatio == null) { + mAspectRatio = Constants.DEFAULT_ASPECT_RATIO; + } + adjustCameraParameters(); + mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation)); + mCallback.onCameraOpened(); + return true; + } catch (RuntimeException e) { + return false; + } + } + + private AspectRatio chooseAspectRatio() { + AspectRatio r = null; + for (AspectRatio ratio : mPreviewSizes.ratios()) { + r = ratio; + if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) { + return ratio; + } + } + return r; + } + + void adjustCameraParameters() { + SortedSet sizes = mPreviewSizes.sizes(mAspectRatio); + if (sizes == null) { // Not supported + mAspectRatio = chooseAspectRatio(); + sizes = mPreviewSizes.sizes(mAspectRatio); + } + Size size = chooseOptimalSize(sizes); + + // Always re-apply camera parameters + // Largest picture size in this ratio + final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last(); + if (mShowingPreview) { + mCamera.stopPreview(); + } + mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight()); + mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); + mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation)); + setAutoFocusInternal(mAutoFocus); + setFlashInternal(mFlash); + setAspectRatio(mAspectRatio); + setZoomInternal(mZoom); + setWhiteBalanceInternal(mWhiteBalance); + setScanningInternal(mIsScanning); + mCamera.setParameters(mCameraParameters); + if (mShowingPreview) { + startCameraPreview(); + } + } + + @SuppressWarnings("SuspiciousNameCombination") + private Size chooseOptimalSize(SortedSet sizes) { + if (!mPreview.isReady()) { // Not yet laid out + return sizes.first(); // Return the smallest size + } + int desiredWidth; + int desiredHeight; + final int surfaceWidth = mPreview.getWidth(); + final int surfaceHeight = mPreview.getHeight(); + if (isLandscape(mDisplayOrientation)) { + desiredWidth = surfaceHeight; + desiredHeight = surfaceWidth; + } else { + desiredWidth = surfaceWidth; + desiredHeight = surfaceHeight; + } + Size result = null; + for (Size size : sizes) { // Iterate from small to large + if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) { + return size; + + } + result = size; + } + return result; + } + + private void releaseCamera() { + if (mCamera != null) { + mCamera.release(); + mCamera = null; + mCallback.onCameraClosed(); + } + } + + /** + * Calculate display orientation + * https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int) + * + * This calculation is used for orienting the preview + * + * Note: This is not the same calculation as the camera rotation + * + * @param screenOrientationDegrees Screen orientation in degrees + * @return Number of degrees required to rotate preview + */ + private int calcDisplayOrientation(int screenOrientationDegrees) { + if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + return (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360; + } else { // back-facing + return (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360; + } + } + + /** + * Calculate camera rotation + * + * This calculation is applied to the output JPEG either via Exif Orientation tag + * or by actually transforming the bitmap. (Determined by vendor camera API implementation) + * + * Note: This is not the same calculation as the display orientation + * + * @param screenOrientationDegrees Screen orientation in degrees + * @return Number of degrees to rotate image in order for it to view correctly. + */ + private int calcCameraRotation(int screenOrientationDegrees) { + if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + return (mCameraInfo.orientation + screenOrientationDegrees) % 360; + } else { // back-facing + final int landscapeFlip = isLandscape(screenOrientationDegrees) ? 180 : 0; + return (mCameraInfo.orientation + screenOrientationDegrees + landscapeFlip) % 360; + } + } + + /** + * Test if the supplied orientation is in landscape. + * + * @param orientationDegrees Orientation in degrees (0,90,180,270) + * @return True if in landscape, false if portrait + */ + private boolean isLandscape(int orientationDegrees) { + return (orientationDegrees == Constants.LANDSCAPE_90 || + orientationDegrees == Constants.LANDSCAPE_270); + } + + /** + * @return {@code true} if {@link #mCameraParameters} was modified. + */ + private boolean setAutoFocusInternal(boolean autoFocus) { + mAutoFocus = autoFocus; + if (isCameraOpened()) { + final List modes = mCameraParameters.getSupportedFocusModes(); + if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); + } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { + mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED); + } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { + mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); + } else { + mCameraParameters.setFocusMode(modes.get(0)); + } + return true; + } else { + return false; + } + } + + /** + * @return {@code true} if {@link #mCameraParameters} was modified. + */ + private boolean setFlashInternal(int flash) { + if (isCameraOpened()) { + List modes = mCameraParameters.getSupportedFlashModes(); + String mode = FLASH_MODES.get(flash); + if (modes != null && modes.contains(mode)) { + mCameraParameters.setFlashMode(mode); + mFlash = flash; + return true; + } + String currentMode = FLASH_MODES.get(mFlash); + if (modes == null || !modes.contains(currentMode)) { + mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); + mFlash = Constants.FLASH_OFF; + return true; + } + return false; + } else { + mFlash = flash; + return false; + } + } + + /** + * @return {@code true} if {@link #mCameraParameters} was modified. + */ + private boolean setZoomInternal(float zoom) { + if (isCameraOpened() && mCameraParameters.isZoomSupported()) { + int maxZoom = mCameraParameters.getMaxZoom(); + int scaledValue = (int) (zoom * maxZoom); + mCameraParameters.setZoom(scaledValue); + mZoom = zoom; + return true; + } else { + mZoom = zoom; + return false; + } + } + + /** + * @return {@code true} if {@link #mCameraParameters} was modified. + */ + private boolean setWhiteBalanceInternal(int whiteBalance) { + mWhiteBalance = whiteBalance; + if (isCameraOpened()) { + final List modes = mCameraParameters.getSupportedWhiteBalance(); + String mode = WB_MODES.get(whiteBalance); + if (modes != null && modes.contains(mode)) { + mCameraParameters.setWhiteBalance(mode); + return true; + } + String currentMode = WB_MODES.get(mWhiteBalance); + if (modes == null || !modes.contains(currentMode)) { + mCameraParameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO); + mWhiteBalance = Constants.WB_AUTO; + return true; + } + return false; + } else { + return false; + } + } + + private void setScanningInternal(boolean isScanning) { + mIsScanning = isScanning; + if (isCameraOpened()) { + if (mIsScanning) { + mCamera.setPreviewCallback(this); + } else { + mCamera.setPreviewCallback(null); + } + } + } + + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + Camera.Size previewSize = mCameraParameters.getPreviewSize(); + mCallback.onFramePreview(data, previewSize.width, previewSize.height, mDisplayOrientation); + } + + private void setUpMediaRecorder(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile) { + mMediaRecorder = new MediaRecorder(); + mCamera.unlock(); + + mMediaRecorder.setCamera(mCamera); + + mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); + if (recordAudio) { + mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); + } + + mMediaRecorder.setOutputFile(path); + mVideoPath = path; + + if (CamcorderProfile.hasProfile(mCameraId, profile.quality)) { + setCamcorderProfile(CamcorderProfile.get(mCameraId, profile.quality), recordAudio); + } else { + setCamcorderProfile(CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH), recordAudio); + } + + mMediaRecorder.setOrientationHint(calcCameraRotation(mDisplayOrientation)); + + if (maxDuration != -1) { + mMediaRecorder.setMaxDuration(maxDuration); + } + if (maxFileSize != -1) { + mMediaRecorder.setMaxFileSize(maxFileSize); + } + + mMediaRecorder.setOnInfoListener(this); + mMediaRecorder.setOnErrorListener(this); + } + + private void stopMediaRecorder() { + mIsRecording = false; + if (mMediaRecorder != null) { + try { + mMediaRecorder.stop(); + } catch (RuntimeException ex) { + ex.printStackTrace(); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + + if (mVideoPath == null || !new File(mVideoPath).exists()) { + mCallback.onVideoRecorded(null); + return; + } + + mCallback.onVideoRecorded(mVideoPath); + mVideoPath = null; + } + + private void setCamcorderProfile(CamcorderProfile profile, boolean recordAudio) { + mMediaRecorder.setOutputFormat(profile.fileFormat); + mMediaRecorder.setVideoFrameRate(profile.videoFrameRate); + mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); + mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate); + mMediaRecorder.setVideoEncoder(profile.videoCodec); + if (recordAudio) { + mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate); + mMediaRecorder.setAudioChannels(profile.audioChannels); + mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate); + mMediaRecorder.setAudioEncoder(profile.audioCodec); + } + } + + @Override + 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) { + stopRecording(); + } + } + + + @Override + public void onError(MediaRecorder mr, int what, int extra) { + stopRecording(); + } +} diff --git a/android/src/main/java/com/google/android/cameraview/Camera2.java b/android/src/main/java/com/google/android/cameraview/Camera2.java new file mode 100644 index 0000000000..585d1c9fa3 --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/Camera2.java @@ -0,0 +1,1208 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.CamcorderProfile; +import android.media.Image; +import android.media.ImageReader; +import android.media.MediaRecorder; +import android.support.annotation.NonNull; +import android.util.Log; +import android.util.SparseIntArray; +import android.view.Surface; +import android.os.Handler; +import android.os.Looper; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.SortedSet; + +@SuppressWarnings("MissingPermission") +@TargetApi(21) +class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, MediaRecorder.OnErrorListener { + + private static final String TAG = "Camera2"; + + private static final SparseIntArray INTERNAL_FACINGS = new SparseIntArray(); + + static { + INTERNAL_FACINGS.put(Constants.FACING_BACK, CameraCharacteristics.LENS_FACING_BACK); + INTERNAL_FACINGS.put(Constants.FACING_FRONT, CameraCharacteristics.LENS_FACING_FRONT); + } + + /** + * Max preview width that is guaranteed by Camera2 API + */ + private static final int MAX_PREVIEW_WIDTH = 1920; + + /** + * Max preview height that is guaranteed by Camera2 API + */ + private static final int MAX_PREVIEW_HEIGHT = 1080; + + private final CameraManager mCameraManager; + + private final CameraDevice.StateCallback mCameraDeviceCallback + = new CameraDevice.StateCallback() { + + @Override + public void onOpened(@NonNull CameraDevice camera) { + mCamera = camera; + mCallback.onCameraOpened(); + startCaptureSession(); + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + mCallback.onCameraClosed(); + } + + @Override + public void onDisconnected(@NonNull CameraDevice camera) { + mCamera = null; + } + + @Override + public void onError(@NonNull CameraDevice camera, int error) { + Log.e(TAG, "onError: " + camera.getId() + " (" + error + ")"); + mCamera = null; + } + + }; + + private final CameraCaptureSession.StateCallback mSessionCallback + = new CameraCaptureSession.StateCallback() { + + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + if (mCamera == null) { + return; + } + mCaptureSession = session; + mInitialCropRegion = mPreviewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION); + updateAutoFocus(); + updateFlash(); + updateFocusDepth(); + updateWhiteBalance(); + updateZoom(); + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e); + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to start camera preview.", e); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession session) { + Log.e(TAG, "Failed to configure capture session."); + } + + @Override + public void onClosed(@NonNull CameraCaptureSession session) { + if (mCaptureSession != null && mCaptureSession.equals(session)) { + mCaptureSession = null; + } + } + + }; + + PictureCaptureCallback mCaptureCallback = new PictureCaptureCallback() { + + @Override + public void onPrecaptureRequired() { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + setState(STATE_PRECAPTURE); + try { + mCaptureSession.capture(mPreviewRequestBuilder.build(), this, null); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to run precapture sequence.", e); + } + } + + @Override + public void onReady() { + captureStillPicture(); + } + + }; + + private final ImageReader.OnImageAvailableListener mOnImageAvailableListener + = new ImageReader.OnImageAvailableListener() { + + @Override + public void onImageAvailable(ImageReader reader) { + try (Image image = reader.acquireNextImage()) { + Image.Plane[] planes = image.getPlanes(); + if (planes.length > 0) { + ByteBuffer buffer = planes[0].getBuffer(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + if (image.getFormat() == ImageFormat.JPEG) { + mCallback.onPictureTaken(data); + } else { + mCallback.onFramePreview(data, image.getWidth(), image.getHeight(), mDisplayOrientation); + } + image.close(); + } + } + } + + }; + + + private String mCameraId; + + private CameraCharacteristics mCameraCharacteristics; + + CameraDevice mCamera; + + CameraCaptureSession mCaptureSession; + + CaptureRequest.Builder mPreviewRequestBuilder; + + Set mAvailableCameras = new HashSet<>(); + + private ImageReader mStillImageReader; + + private ImageReader mScanImageReader; + + private int mImageFormat; + + private MediaRecorder mMediaRecorder; + + private String mVideoPath; + + private boolean mIsRecording; + + private final SizeMap mPreviewSizes = new SizeMap(); + + private final SizeMap mPictureSizes = new SizeMap(); + + private int mFacing; + + private AspectRatio mAspectRatio = Constants.DEFAULT_ASPECT_RATIO; + + private AspectRatio mInitialRatio; + + private boolean mAutoFocus; + + private int mFlash; + + private int mDisplayOrientation; + + private float mFocusDepth; + + private float mZoom; + + private int mWhiteBalance; + + private boolean mIsScanning; + + private Surface mPreviewSurface; + + private Rect mInitialCropRegion; + + Camera2(Callback callback, PreviewImpl preview, Context context) { + super(callback, preview); + mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); + mCameraManager.registerAvailabilityCallback(new CameraManager.AvailabilityCallback() { + @Override + public void onCameraAvailable(@NonNull String cameraId) { + super.onCameraAvailable(cameraId); + mAvailableCameras.add(cameraId); + } + + @Override + public void onCameraUnavailable(@NonNull String cameraId) { + super.onCameraUnavailable(cameraId); + mAvailableCameras.remove(cameraId); + } + }, null); + mImageFormat = mIsScanning ? ImageFormat.YUV_420_888 : ImageFormat.JPEG; + mPreview.setCallback(new PreviewImpl.Callback() { + @Override + public void onSurfaceChanged() { + startCaptureSession(); + } + + @Override + public void onSurfaceDestroyed() { + stop(); + } + }); + } + + @Override + boolean start() { + if (!chooseCameraIdByFacing()) { + mAspectRatio = mInitialRatio; + return false; + } + collectCameraInfo(); + setAspectRatio(mInitialRatio); + mInitialRatio = null; + prepareStillImageReader(); + prepareScanImageReader(); + startOpeningCamera(); + return true; + } + + @Override + void stop() { + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + if (mCamera != null) { + mCamera.close(); + mCamera = null; + } + if (mStillImageReader != null) { + mStillImageReader.close(); + mStillImageReader = null; + } + + if (mScanImageReader != null) { + mScanImageReader.close(); + mScanImageReader = null; + } + + if (mMediaRecorder != null) { + mMediaRecorder.stop(); + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + + if (mIsRecording) { + mCallback.onVideoRecorded(mVideoPath); + mIsRecording = false; + } + } + } + + @Override + boolean isCameraOpened() { + return mCamera != null; + } + + @Override + void setFacing(int facing) { + if (mFacing == facing) { + return; + } + mFacing = facing; + if (isCameraOpened()) { + stop(); + start(); + } + } + + @Override + int getFacing() { + return mFacing; + } + + @Override + Set getSupportedAspectRatios() { + return mPreviewSizes.ratios(); + } + + @Override + boolean setAspectRatio(AspectRatio ratio) { + if (ratio != null && mPreviewSizes.isEmpty()) { + mInitialRatio = ratio; + return false; + } + if (ratio == null || ratio.equals(mAspectRatio) || + !mPreviewSizes.ratios().contains(ratio)) { + // TODO: Better error handling + return false; + } + mAspectRatio = ratio; + prepareStillImageReader(); + prepareScanImageReader(); + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + startCaptureSession(); + } + return true; + } + + @Override + AspectRatio getAspectRatio() { + return mAspectRatio; + } + + @Override + void setAutoFocus(boolean autoFocus) { + if (mAutoFocus == autoFocus) { + return; + } + mAutoFocus = autoFocus; + if (mPreviewRequestBuilder != null) { + updateAutoFocus(); + if (mCaptureSession != null) { + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + mAutoFocus = !mAutoFocus; // Revert + } + } + } + } + + @Override + boolean getAutoFocus() { + return mAutoFocus; + } + + @Override + void setFlash(int flash) { + if (mFlash == flash) { + return; + } + int saved = mFlash; + mFlash = flash; + if (mPreviewRequestBuilder != null) { + updateFlash(); + if (mCaptureSession != null) { + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + mFlash = saved; // Revert + } + } + } + } + + @Override + int getFlash() { + return mFlash; + } + + @Override + void takePicture() { + if (mAutoFocus) { + lockFocus(); + } else { + captureStillPicture(); + } + } + + @Override + boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile) { + if (!mIsRecording) { + setUpMediaRecorder(path, maxDuration, maxFileSize, recordAudio, profile); + try { + mMediaRecorder.prepare(); + + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + + Size size = chooseOptimalSize(); + mPreview.setBufferSize(size.getWidth(), size.getHeight()); + Surface surface = getPreviewSurface(); + Surface mMediaRecorderSurface = mMediaRecorder.getSurface(); + + mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); + mPreviewRequestBuilder.addTarget(surface); + mPreviewRequestBuilder.addTarget(mMediaRecorderSurface); + mCamera.createCaptureSession(Arrays.asList(surface, mMediaRecorderSurface), + mSessionCallback, null); + mMediaRecorder.start(); + mIsRecording = true; + return true; + } catch (CameraAccessException | IOException e) { + e.printStackTrace(); + return false; + } + } + return false; + } + + @Override + void stopRecording() { + if (mIsRecording) { + stopMediaRecorder(); + + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + startCaptureSession(); + } + } + + @Override + public void setFocusDepth(float value) { + if (mFocusDepth == value) { + return; + } + float saved = mFocusDepth; + mFocusDepth = value; + if (mCaptureSession != null) { + updateFocusDepth(); + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + mFocusDepth = saved; // Revert + } + } + } + + @Override + float getFocusDepth() { + return mFocusDepth; + } + + @Override + public void setZoom(float zoom) { + if (mZoom == zoom) { + return; + } + float saved = mZoom; + mZoom = zoom; + if (mCaptureSession != null) { + updateZoom(); + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + mZoom = saved; // Revert + } + } + } + + @Override + float getZoom() { + return mZoom; + } + + @Override + public void setWhiteBalance(int whiteBalance) { + if (mWhiteBalance == whiteBalance) { + return; + } + int saved = mWhiteBalance; + mWhiteBalance = whiteBalance; + if (mCaptureSession != null) { + updateWhiteBalance(); + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + mWhiteBalance = saved; // Revert + } + } + } + + @Override + public int getWhiteBalance() { + return mWhiteBalance; + } + + @Override + void setScanning(boolean isScanning) { + if (mIsScanning == isScanning) { + return; + } + mIsScanning = isScanning; + if (!mIsScanning) { + mImageFormat = ImageFormat.JPEG; + } else { + mImageFormat = ImageFormat.YUV_420_888; + } + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + startCaptureSession(); + } + + @Override + boolean getScanning() { + return mIsScanning; + } + + @Override + void setDisplayOrientation(int displayOrientation) { + mDisplayOrientation = displayOrientation; + mPreview.setDisplayOrientation(mDisplayOrientation); + } + + /** + *

Chooses a camera ID by the specified camera facing ({@link #mFacing}).

+ *

This rewrites {@link #mCameraId}, {@link #mCameraCharacteristics}, and optionally + * {@link #mFacing}.

+ */ + private boolean chooseCameraIdByFacing() { + try { + int internalFacing = INTERNAL_FACINGS.get(mFacing); + final String[] ids = mCameraManager.getCameraIdList(); + if (ids.length == 0) { // No camera + throw new RuntimeException("No camera available."); + } + for (String id : ids) { + CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id); + Integer level = characteristics.get( + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + if (level == null || + level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { + continue; + } + Integer internal = characteristics.get(CameraCharacteristics.LENS_FACING); + if (internal == null) { + throw new NullPointerException("Unexpected state: LENS_FACING null"); + } + if (internal == internalFacing) { + mCameraId = id; + mCameraCharacteristics = characteristics; + return true; + } + } + // Not found + mCameraId = ids[0]; + mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId); + Integer level = mCameraCharacteristics.get( + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + if (level == null || + level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { + return false; + } + Integer internal = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING); + if (internal == null) { + throw new NullPointerException("Unexpected state: LENS_FACING null"); + } + for (int i = 0, count = INTERNAL_FACINGS.size(); i < count; i++) { + if (INTERNAL_FACINGS.valueAt(i) == internal) { + mFacing = INTERNAL_FACINGS.keyAt(i); + return true; + } + } + // The operation can reach here when the only camera device is an external one. + // We treat it as facing back. + mFacing = Constants.FACING_BACK; + return true; + } catch (CameraAccessException e) { + throw new RuntimeException("Failed to get a list of camera devices", e); + } + } + + /** + *

Collects some information from {@link #mCameraCharacteristics}.

+ *

This rewrites {@link #mPreviewSizes}, {@link #mPictureSizes}, and optionally, + * {@link #mAspectRatio}.

+ */ + private void collectCameraInfo() { + StreamConfigurationMap map = mCameraCharacteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + if (map == null) { + throw new IllegalStateException("Failed to get configuration map: " + mCameraId); + } + mPreviewSizes.clear(); + for (android.util.Size size : map.getOutputSizes(mPreview.getOutputClass())) { + int width = size.getWidth(); + int height = size.getHeight(); + if (width <= MAX_PREVIEW_WIDTH && height <= MAX_PREVIEW_HEIGHT) { + mPreviewSizes.add(new Size(width, height)); + } + } + mPictureSizes.clear(); + collectPictureSizes(mPictureSizes, map); + for (AspectRatio ratio : mPreviewSizes.ratios()) { + if (!mPictureSizes.ratios().contains(ratio)) { + mPreviewSizes.remove(ratio); + } + } + + if (!mPreviewSizes.ratios().contains(mAspectRatio)) { + mAspectRatio = mPreviewSizes.ratios().iterator().next(); + } + } + + protected void collectPictureSizes(SizeMap sizes, StreamConfigurationMap map) { + for (android.util.Size size : map.getOutputSizes(mImageFormat)) { + mPictureSizes.add(new Size(size.getWidth(), size.getHeight())); + } + } + + private void prepareStillImageReader() { + if (mStillImageReader != null) { + mStillImageReader.close(); + } + Size largest = mPictureSizes.sizes(mAspectRatio).last(); + mStillImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), + ImageFormat.JPEG, 1); + mStillImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); + } + + private void prepareScanImageReader() { + if (mScanImageReader != null) { + mScanImageReader.close(); + } + Size largest = mPreviewSizes.sizes(mAspectRatio).last(); + mScanImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), + ImageFormat.YUV_420_888, 1); + mScanImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); + } + + /** + *

Starts opening a camera device.

+ *

The result will be processed in {@link #mCameraDeviceCallback}.

+ */ + private void startOpeningCamera() { + try { + mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, null); + } catch (CameraAccessException e) { + throw new RuntimeException("Failed to open camera: " + mCameraId, e); + } + } + + /** + *

Starts a capture session for camera preview.

+ *

This rewrites {@link #mPreviewRequestBuilder}.

+ *

The result will be continuously processed in {@link #mSessionCallback}.

+ */ + void startCaptureSession() { + if (!isCameraOpened() || !mPreview.isReady() || mStillImageReader == null || mScanImageReader == null) { + return; + } + Size previewSize = chooseOptimalSize(); + mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight()); + Surface surface = getPreviewSurface(); + try { + mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + mPreviewRequestBuilder.addTarget(surface); + + if (mIsScanning) { + mPreviewRequestBuilder.addTarget(mScanImageReader.getSurface()); + } + mCamera.createCaptureSession(Arrays.asList(surface, mStillImageReader.getSurface(), + mScanImageReader.getSurface()), mSessionCallback, null); + } catch (CameraAccessException e) { + mCallback.onMountError(); + } + } + + public Surface getPreviewSurface() { + if (mPreviewSurface != null) { + return mPreviewSurface; + } + return mPreview.getSurface(); + } + + @Override + public void setPreviewTexture(SurfaceTexture surfaceTexture) { + if (surfaceTexture != null) { + Surface previewSurface = new Surface(surfaceTexture); + mPreviewSurface = previewSurface; + } else { + mPreviewSurface = null; + } + + // it may be called from another thread, so make sure we're in main looper + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + startCaptureSession(); + } + }); + } + + @Override + public Size getPreviewSize() { + return new Size(mPreview.getWidth(), mPreview.getHeight()); + } + + /** + * Chooses the optimal preview size based on {@link #mPreviewSizes} and the surface size. + * + * @return The picked size for camera preview. + */ + private Size chooseOptimalSize() { + int surfaceLonger, surfaceShorter; + final int surfaceWidth = mPreview.getWidth(); + final int surfaceHeight = mPreview.getHeight(); + if (surfaceWidth < surfaceHeight) { + surfaceLonger = surfaceHeight; + surfaceShorter = surfaceWidth; + } else { + surfaceLonger = surfaceWidth; + surfaceShorter = surfaceHeight; + } + SortedSet candidates = mPreviewSizes.sizes(mAspectRatio); + + // Pick the smallest of those big enough + for (Size size : candidates) { + if (size.getWidth() >= surfaceLonger && size.getHeight() >= surfaceShorter) { + return size; + } + } + // If no size is big enough, pick the largest one. + return candidates.last(); + } + + /** + * Updates the internal state of auto-focus to {@link #mAutoFocus}. + */ + void updateAutoFocus() { + if (mAutoFocus) { + int[] modes = mCameraCharacteristics.get( + CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); + // Auto focus is not supported + if (modes == null || modes.length == 0 || + (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)) { + mAutoFocus = false; + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_OFF); + } else { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + } + } else { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_OFF); + } + } + + /** + * Updates the internal state of flash to {@link #mFlash}. + */ + void updateFlash() { + switch (mFlash) { + case Constants.FLASH_OFF: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_ON: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_TORCH: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_TORCH); + break; + case Constants.FLASH_AUTO: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_RED_EYE: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + } + } + + /** + * Updates the internal state of focus depth to {@link #mFocusDepth}. + */ + void updateFocusDepth() { + if (mAutoFocus) { + return; + } + Float minimumLens = mCameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); + if (minimumLens == null) { + throw new NullPointerException("Unexpected state: LENS_INFO_MINIMUM_FOCUS_DISTANCE null"); + } + float value = mFocusDepth * minimumLens; + mPreviewRequestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, value); + } + + /** + * Updates the internal state of zoom to {@link #mZoom}. + */ + void updateZoom() { + float maxZoom = mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); + float scaledZoom = mZoom * (maxZoom - 1.0f) + 1.0f; + Rect currentPreview = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + if (currentPreview != null) { + int currentWidth = currentPreview.width(); + int currentHeight = currentPreview.height(); + int zoomedWidth = (int) (currentWidth / scaledZoom); + int zoomedHeight = (int) (currentHeight / scaledZoom); + int widthOffset = (currentWidth - zoomedWidth) / 2; + int heightOffset = (currentHeight - zoomedHeight) / 2; + + Rect zoomedPreview = new Rect( + currentPreview.left + widthOffset, + currentPreview.top + heightOffset, + currentPreview.right - widthOffset, + currentPreview.bottom - heightOffset + ); + + // ¯\_(ツ)_/¯ for some devices calculating the Rect for zoom=1 results in a bit different + // Rect that device claims as its no-zoom crop region and the preview freezes + if (scaledZoom != 1.0f) { + mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomedPreview); + } else { + mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mInitialCropRegion); + } + } + } + + /** + * Updates the internal state of white balance to {@link #mWhiteBalance}. + */ + void updateWhiteBalance() { + switch (mWhiteBalance) { + case Constants.WB_AUTO: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, + CaptureRequest.CONTROL_AWB_MODE_AUTO); + break; + case Constants.WB_CLOUDY: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, + CaptureRequest.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); + break; + case Constants.WB_FLUORESCENT: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, + CaptureRequest.CONTROL_AWB_MODE_FLUORESCENT); + break; + case Constants.WB_INCANDESCENT: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, + CaptureRequest.CONTROL_AWB_MODE_INCANDESCENT); + break; + case Constants.WB_SHADOW: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, + CaptureRequest.CONTROL_AWB_MODE_SHADE); + break; + case Constants.WB_SUNNY: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, + CaptureRequest.CONTROL_AWB_MODE_DAYLIGHT); + break; + } + } + + /** + * Locks the focus as the first step for a still image capture. + */ + private void lockFocus() { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_START); + try { + mCaptureCallback.setState(PictureCaptureCallback.STATE_LOCKING); + mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to lock focus.", e); + } + } + + /** + * Captures a still picture. + */ + void captureStillPicture() { + try { + CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest( + CameraDevice.TEMPLATE_STILL_CAPTURE); + if (mIsScanning) { + mImageFormat = ImageFormat.JPEG; + captureRequestBuilder.removeTarget(mScanImageReader.getSurface()); + } + captureRequestBuilder.addTarget(mStillImageReader.getSurface()); + captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE)); + switch (mFlash) { + case Constants.FLASH_OFF: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_ON: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + break; + case Constants.FLASH_TORCH: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_TORCH); + break; + case Constants.FLASH_AUTO: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + case Constants.FLASH_RED_EYE: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOutputRotation()); + captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mPreviewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION)); + // Stop preview and capture a still picture. + mCaptureSession.stopRepeating(); + mCaptureSession.capture(captureRequestBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + unlockFocus(); + } + }, null); + } catch (CameraAccessException e) { + Log.e(TAG, "Cannot capture a still picture.", e); + } + } + + private int getOutputRotation() { + @SuppressWarnings("ConstantConditions") + int sensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + return (sensorOrientation + + mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) + + 360) % 360; + } + + private void setUpMediaRecorder(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile) { + mMediaRecorder = new MediaRecorder(); + + mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); + if (recordAudio) { + mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + } + + mMediaRecorder.setOutputFile(path); + mVideoPath = path; + + if (CamcorderProfile.hasProfile(Integer.parseInt(mCameraId), profile.quality)) { + setCamcorderProfile(profile, recordAudio); + } else { + setCamcorderProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH), recordAudio); + } + + mMediaRecorder.setOrientationHint(getOutputRotation()); + + if (maxDuration != -1) { + mMediaRecorder.setMaxDuration(maxDuration); + } + if (maxFileSize != -1) { + mMediaRecorder.setMaxFileSize(maxFileSize); + } + + mMediaRecorder.setOnInfoListener(this); + mMediaRecorder.setOnErrorListener(this); + } + + private void setCamcorderProfile(CamcorderProfile profile, boolean recordAudio) { + mMediaRecorder.setOutputFormat(profile.fileFormat); + mMediaRecorder.setVideoFrameRate(profile.videoFrameRate); + mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); + mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate); + mMediaRecorder.setVideoEncoder(profile.videoCodec); + if (recordAudio) { + mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate); + mMediaRecorder.setAudioChannels(profile.audioChannels); + mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate); + mMediaRecorder.setAudioEncoder(profile.audioCodec); + } + } + + private void stopMediaRecorder() { + mIsRecording = false; + try { + mCaptureSession.stopRepeating(); + mCaptureSession.abortCaptures(); + mMediaRecorder.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + + if (mVideoPath == null || !new File(mVideoPath).exists()) { + mCallback.onVideoRecorded(null); + return; + } + mCallback.onVideoRecorded(mVideoPath); + mVideoPath = null; + } + + /** + * Unlocks the auto-focus and restart camera preview. This is supposed to be called after + * capturing a still picture. + */ + void unlockFocus() { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); + try { + mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null); + updateAutoFocus(); + updateFlash(); + if (mIsScanning) { + mImageFormat = ImageFormat.YUV_420_888; + startCaptureSession(); + } else { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_IDLE); + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, + null); + mCaptureCallback.setState(PictureCaptureCallback.STATE_PREVIEW); + } + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to restart camera preview.", e); + } + } + + /** + * Called when an something occurs while recording. + */ + 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) { + stopRecording(); + } + } + + /** + * Called when an error occurs while recording. + */ + public void onError(MediaRecorder mr, int what, int extra) { + stopRecording(); + } + + /** + * A {@link CameraCaptureSession.CaptureCallback} for capturing a still picture. + */ + private static abstract class PictureCaptureCallback + extends CameraCaptureSession.CaptureCallback { + + static final int STATE_PREVIEW = 0; + static final int STATE_LOCKING = 1; + static final int STATE_LOCKED = 2; + static final int STATE_PRECAPTURE = 3; + static final int STATE_WAITING = 4; + static final int STATE_CAPTURING = 5; + + private int mState; + + PictureCaptureCallback() { + } + + void setState(int state) { + mState = state; + } + + @Override + public void onCaptureProgressed(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { + process(partialResult); + } + + @Override + public void onCaptureCompleted(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + process(result); + } + + private void process(@NonNull CaptureResult result) { + switch (mState) { + case STATE_LOCKING: { + Integer af = result.get(CaptureResult.CONTROL_AF_STATE); + if (af == null) { + break; + } + if (af == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || + af == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { + Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); + if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { + setState(STATE_CAPTURING); + onReady(); + } else { + setState(STATE_LOCKED); + onPrecaptureRequired(); + } + } + break; + } + case STATE_PRECAPTURE: { + Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); + if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || + ae == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED || + ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { + setState(STATE_WAITING); + } + break; + } + case STATE_WAITING: { + Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); + if (ae == null || ae != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { + setState(STATE_CAPTURING); + onReady(); + } + break; + } + } + } + + /** + * Called when it is ready to take a still picture. + */ + public abstract void onReady(); + + /** + * Called when it is necessary to run the precapture sequence. + */ + public abstract void onPrecaptureRequired(); + + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/Camera2Api23.java b/android/src/main/java/com/google/android/cameraview/Camera2Api23.java new file mode 100644 index 0000000000..176c1e25b4 --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/Camera2Api23.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.ImageFormat; +import android.hardware.camera2.params.StreamConfigurationMap; + + +@TargetApi(23) +class Camera2Api23 extends Camera2 { + + Camera2Api23(Callback callback, PreviewImpl preview, Context context) { + super(callback, preview, context); + } + + @Override + protected void collectPictureSizes(SizeMap sizes, StreamConfigurationMap map) { + // Try to get hi-res output sizes + android.util.Size[] outputSizes = map.getHighResolutionOutputSizes(ImageFormat.JPEG); + if (outputSizes != null) { + for (android.util.Size size : map.getHighResolutionOutputSizes(ImageFormat.JPEG)) { + sizes.add(new Size(size.getWidth(), size.getHeight())); + } + } + if (sizes.isEmpty()) { + super.collectPictureSizes(sizes, map); + } + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/CameraView.java b/android/src/main/java/com/google/android/cameraview/CameraView.java new file mode 100644 index 0000000000..43297b6fee --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/CameraView.java @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.app.Activity; +import android.content.Context; +import android.media.CamcorderProfile; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.os.ParcelableCompat; +import android.support.v4.os.ParcelableCompatCreatorCallbacks; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.graphics.SurfaceTexture; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Set; + +public class CameraView extends FrameLayout { + + /** The camera device faces the opposite direction as the device's screen. */ + public static final int FACING_BACK = Constants.FACING_BACK; + + /** The camera device faces the same direction as the device's screen. */ + public static final int FACING_FRONT = Constants.FACING_FRONT; + + /** Direction the camera faces relative to device screen. */ + @IntDef({FACING_BACK, FACING_FRONT}) + @Retention(RetentionPolicy.SOURCE) + public @interface Facing { + } + + /** Flash will not be fired. */ + public static final int FLASH_OFF = Constants.FLASH_OFF; + + /** Flash will always be fired during snapshot. */ + public static final int FLASH_ON = Constants.FLASH_ON; + + /** Constant emission of light during preview, auto-focus and snapshot. */ + public static final int FLASH_TORCH = Constants.FLASH_TORCH; + + /** Flash will be fired automatically when required. */ + public static final int FLASH_AUTO = Constants.FLASH_AUTO; + + /** Flash will be fired in red-eye reduction mode. */ + public static final int FLASH_RED_EYE = Constants.FLASH_RED_EYE; + + /** The mode for for the camera device's flash control */ + @IntDef({FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE}) + public @interface Flash { + } + + CameraViewImpl mImpl; + + private final CallbackBridge mCallbacks; + + private boolean mAdjustViewBounds; + + private Context mContext; + + private final DisplayOrientationDetector mDisplayOrientationDetector; + + public CameraView(Context context, boolean fallbackToOldApi) { + this(context, null, fallbackToOldApi); + } + + public CameraView(Context context, AttributeSet attrs, boolean fallbackToOldApi) { + this(context, attrs, 0, fallbackToOldApi); + } + + @SuppressWarnings("WrongConstant") + public CameraView(Context context, AttributeSet attrs, int defStyleAttr, boolean fallbackToOldApi) { + super(context, attrs, defStyleAttr); + if (isInEditMode()){ + mCallbacks = null; + mDisplayOrientationDetector = null; + return; + } + mAdjustViewBounds = true; + mContext = context; + + // Internal setup + final PreviewImpl preview = createPreviewImpl(context); + mCallbacks = new CallbackBridge(); + if (fallbackToOldApi || Build.VERSION.SDK_INT < 21) { + mImpl = new Camera1(mCallbacks, preview); + } else if (Build.VERSION.SDK_INT < 23) { + mImpl = new Camera2(mCallbacks, preview, context); + } else { + mImpl = new Camera2Api23(mCallbacks, preview, context); + } + + // Display orientation detector + mDisplayOrientationDetector = new DisplayOrientationDetector(context) { + @Override + public void onDisplayOrientationChanged(int displayOrientation) { + mImpl.setDisplayOrientation(displayOrientation); + } + }; + } + + @NonNull + private PreviewImpl createPreviewImpl(Context context) { + PreviewImpl preview; + if (Build.VERSION.SDK_INT < 14) { + preview = new SurfaceViewPreview(context, this); + } else { + preview = new TextureViewPreview(context, this); + } + return preview; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (!isInEditMode()) { + mDisplayOrientationDetector.enable(ViewCompat.getDisplay(this)); + } + } + + @Override + protected void onDetachedFromWindow() { + if (!isInEditMode()) { + mDisplayOrientationDetector.disable(); + } + super.onDetachedFromWindow(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (isInEditMode()){ + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + // Handle android:adjustViewBounds + if (mAdjustViewBounds) { + if (!isCameraOpened()) { + mCallbacks.reserveRequestLayoutOnOpen(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) { + final AspectRatio ratio = getAspectRatio(); + assert ratio != null; + int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat()); + if (heightMode == MeasureSpec.AT_MOST) { + height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec)); + } + super.onMeasure(widthMeasureSpec, + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) { + final AspectRatio ratio = getAspectRatio(); + assert ratio != null; + int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat()); + if (widthMode == MeasureSpec.AT_MOST) { + width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec)); + } + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + heightMeasureSpec); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + // Measure the TextureView + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + AspectRatio ratio = getAspectRatio(); + if (mDisplayOrientationDetector.getLastKnownDisplayOrientation() % 180 == 0) { + ratio = ratio.inverse(); + } + assert ratio != null; + if (height < width * ratio.getY() / ratio.getX()) { + mImpl.getView().measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(width * ratio.getY() / ratio.getX(), + MeasureSpec.EXACTLY)); + } else { + mImpl.getView().measure( + MeasureSpec.makeMeasureSpec(height * ratio.getX() / ratio.getY(), + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + } + + @Override + protected Parcelable onSaveInstanceState() { + SavedState state = new SavedState(super.onSaveInstanceState()); + state.facing = getFacing(); + state.ratio = getAspectRatio(); + state.autoFocus = getAutoFocus(); + state.flash = getFlash(); + state.focusDepth = getFocusDepth(); + state.zoom = getZoom(); + state.whiteBalance = getWhiteBalance(); + state.scanning = getScanning(); + return state; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + setFacing(ss.facing); + setAspectRatio(ss.ratio); + setAutoFocus(ss.autoFocus); + setFlash(ss.flash); + setFocusDepth(ss.focusDepth); + setZoom(ss.zoom); + setWhiteBalance(ss.whiteBalance); + setScanning(ss.scanning); + } + + public void setUsingCamera2Api(boolean useCamera2) { + if (Build.VERSION.SDK_INT < 21) { + return; + } + + boolean wasOpened = isCameraOpened(); + Parcelable state = onSaveInstanceState(); + + if (useCamera2) { + if (wasOpened) { + stop(); + } + if (Build.VERSION.SDK_INT < 23) { + mImpl = new Camera2(mCallbacks, mImpl.mPreview, mContext); + } else { + mImpl = new Camera2Api23(mCallbacks, mImpl.mPreview, mContext); + } + } else { + if (mImpl instanceof Camera1) { + return; + } + + if (wasOpened) { + stop(); + } + mImpl = new Camera1(mCallbacks, mImpl.mPreview); + } + onRestoreInstanceState(state); + if (wasOpened) { + start(); + } + } + + /** + * Open a camera device and start showing camera preview. This is typically called from + * {@link Activity#onResume()}. + */ + public void start() { + if (!mImpl.start()) { + if (mImpl.getView() != null) { + this.removeView(mImpl.getView()); + } + //store the state and restore this state after fall back to Camera1 + Parcelable state=onSaveInstanceState(); + // Camera2 uses legacy hardware layer; fall back to Camera1 + mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext())); + onRestoreInstanceState(state); + mImpl.start(); + } + } + + /** + * Stop camera preview and close the device. This is typically called from + * {@link Activity#onPause()}. + */ + public void stop() { + mImpl.stop(); + } + + /** + * @return {@code true} if the camera is opened. + */ + public boolean isCameraOpened() { + return mImpl.isCameraOpened(); + } + + /** + * Add a new callback. + * + * @param callback The {@link Callback} to add. + * @see #removeCallback(Callback) + */ + public void addCallback(@NonNull Callback callback) { + mCallbacks.add(callback); + } + + /** + * Remove a callback. + * + * @param callback The {@link Callback} to remove. + * @see #addCallback(Callback) + */ + public void removeCallback(@NonNull Callback callback) { + mCallbacks.remove(callback); + } + + /** + * @param adjustViewBounds {@code true} if you want the CameraView to adjust its bounds to + * preserve the aspect ratio of camera. + * @see #getAdjustViewBounds() + */ + public void setAdjustViewBounds(boolean adjustViewBounds) { + if (mAdjustViewBounds != adjustViewBounds) { + mAdjustViewBounds = adjustViewBounds; + requestLayout(); + } + } + + /** + * @return True when this CameraView is adjusting its bounds to preserve the aspect ratio of + * camera. + * @see #setAdjustViewBounds(boolean) + */ + public boolean getAdjustViewBounds() { + return mAdjustViewBounds; + } + + public View getView() { + if (mImpl != null) { + return mImpl.getView(); + } + return null; + } + + /** + * Chooses camera by the direction it faces. + * + * @param facing The camera facing. Must be either {@link #FACING_BACK} or + * {@link #FACING_FRONT}. + */ + public void setFacing(@Facing int facing) { + mImpl.setFacing(facing); + } + + /** + * Gets the direction that the current camera faces. + * + * @return The camera facing. + */ + @Facing + public int getFacing() { + //noinspection WrongConstant + return mImpl.getFacing(); + } + + /** + * Gets all the aspect ratios supported by the current camera. + */ + public Set getSupportedAspectRatios() { + return mImpl.getSupportedAspectRatios(); + } + + /** + * Sets the aspect ratio of camera. + * + * @param ratio The {@link AspectRatio} to be set. + */ + public void setAspectRatio(@NonNull AspectRatio ratio) { + if (mImpl.setAspectRatio(ratio)) { + requestLayout(); + } + } + + /** + * Gets the current aspect ratio of camera. + * + * @return The current {@link AspectRatio}. Can be {@code null} if no camera is opened yet. + */ + @Nullable + public AspectRatio getAspectRatio() { + return mImpl.getAspectRatio(); + } + + /** + * Enables or disables the continuous auto-focus mode. When the current camera doesn't support + * auto-focus, calling this method will be ignored. + * + * @param autoFocus {@code true} to enable continuous auto-focus mode. {@code false} to + * disable it. + */ + public void setAutoFocus(boolean autoFocus) { + mImpl.setAutoFocus(autoFocus); + } + + /** + * Returns whether the continuous auto-focus mode is enabled. + * + * @return {@code true} if the continuous auto-focus mode is enabled. {@code false} if it is + * disabled, or if it is not supported by the current camera. + */ + public boolean getAutoFocus() { + return mImpl.getAutoFocus(); + } + + /** + * Sets the flash mode. + * + * @param flash The desired flash mode. + */ + public void setFlash(@Flash int flash) { + mImpl.setFlash(flash); + } + + /** + * Gets the current flash mode. + * + * @return The current flash mode. + */ + @Flash + public int getFlash() { + //noinspection WrongConstant + return mImpl.getFlash(); + } + + public void setFocusDepth(float value) { + mImpl.setFocusDepth(value); + } + + public float getFocusDepth() { return mImpl.getFocusDepth(); } + + public void setZoom(float zoom) { + mImpl.setZoom(zoom); + } + + public float getZoom() { + return mImpl.getZoom(); + } + + public void setWhiteBalance(int whiteBalance) { + mImpl.setWhiteBalance(whiteBalance); + } + + public int getWhiteBalance() { + return mImpl.getWhiteBalance(); + } + + public void setScanning(boolean isScanning) { mImpl.setScanning(isScanning);} + + public boolean getScanning() { return mImpl.getScanning(); } + + /** + * Take a picture. The result will be returned to + * {@link Callback#onPictureTaken(CameraView, byte[])}. + */ + public void takePicture() { + mImpl.takePicture(); + } + + /** + * Record a video and save it to file. The result will be returned to + * {@link Callback#onVideoRecorded(CameraView, String)}. + * @param path Path to file that video will be saved to. + * @param maxDuration Maximum duration of the recording, in seconds. + * @param maxFileSize Maximum recording file size, in bytes. + * @param profile Quality profile of the recording. + */ + public boolean record(String path, int maxDuration, int maxFileSize, + boolean recordAudio, CamcorderProfile profile) { + return mImpl.record(path, maxDuration, maxFileSize, recordAudio, profile); + } + + public void stopRecording() { + mImpl.stopRecording(); + } + + public void setPreviewTexture(SurfaceTexture surfaceTexture) { + mImpl.setPreviewTexture(surfaceTexture); + } + + public Size getPreviewSize() { + return mImpl.getPreviewSize(); + } + + private class CallbackBridge implements CameraViewImpl.Callback { + + private final ArrayList mCallbacks = new ArrayList<>(); + + private boolean mRequestLayoutOnOpen; + + CallbackBridge() { + } + + public void add(Callback callback) { + mCallbacks.add(callback); + } + + public void remove(Callback callback) { + mCallbacks.remove(callback); + } + + @Override + public void onCameraOpened() { + if (mRequestLayoutOnOpen) { + mRequestLayoutOnOpen = false; + requestLayout(); + } + for (Callback callback : mCallbacks) { + callback.onCameraOpened(CameraView.this); + } + } + + @Override + public void onCameraClosed() { + for (Callback callback : mCallbacks) { + callback.onCameraClosed(CameraView.this); + } + } + + @Override + public void onPictureTaken(byte[] data) { + for (Callback callback : mCallbacks) { + callback.onPictureTaken(CameraView.this, data); + } + } + + @Override + public void onVideoRecorded(String path) { + for (Callback callback : mCallbacks) { + callback.onVideoRecorded(CameraView.this, path); + } + } + + @Override + public void onFramePreview(byte[] data, int width, int height, int orientation) { + for (Callback callback : mCallbacks) { + callback.onFramePreview(CameraView.this, data, width, height, orientation); + } + } + + @Override + public void onMountError() { + for (Callback callback : mCallbacks) { + callback.onMountError(CameraView.this); + } + } + + public void reserveRequestLayoutOnOpen() { + mRequestLayoutOnOpen = true; + } + } + + protected static class SavedState extends BaseSavedState { + + @Facing + int facing; + + AspectRatio ratio; + + boolean autoFocus; + + @Flash + int flash; + + float focusDepth; + + float zoom; + + int whiteBalance; + + boolean scanning; + + @SuppressWarnings("WrongConstant") + public SavedState(Parcel source, ClassLoader loader) { + super(source); + facing = source.readInt(); + ratio = source.readParcelable(loader); + autoFocus = source.readByte() != 0; + flash = source.readInt(); + focusDepth = source.readFloat(); + zoom = source.readFloat(); + whiteBalance = source.readInt(); + scanning = source.readByte() != 0; + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(facing); + out.writeParcelable(ratio, 0); + out.writeByte((byte) (autoFocus ? 1 : 0)); + out.writeInt(flash); + out.writeFloat(focusDepth); + out.writeFloat(zoom); + out.writeInt(whiteBalance); + out.writeByte((byte) (scanning ? 1 : 0)); + } + + public static final Creator CREATOR + = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks() { + + @Override + public SavedState createFromParcel(Parcel in, ClassLoader loader) { + return new SavedState(in, loader); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + + }); + + } + + /** + * Callback for monitoring events about {@link CameraView}. + */ + @SuppressWarnings("UnusedParameters") + public abstract static class Callback { + + /** + * Called when camera is opened. + * + * @param cameraView The associated {@link CameraView}. + */ + public void onCameraOpened(CameraView cameraView) { + } + + /** + * Called when camera is closed. + * + * @param cameraView The associated {@link CameraView}. + */ + public void onCameraClosed(CameraView cameraView) { + } + + /** + * Called when a picture is taken. + * + * @param cameraView The associated {@link CameraView}. + * @param data JPEG data. + */ + public void onPictureTaken(CameraView cameraView, byte[] data) { + } + + /** + * Called when a video is recorded. + * + * @param cameraView The associated {@link CameraView}. + * @param path Path to recoredd video file. + */ + public void onVideoRecorded(CameraView cameraView, String path) { + } + + public void onFramePreview(CameraView cameraView, byte[] data, int width, int height, int orientation) { + } + + public void onMountError(CameraView cameraView) {} + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/CameraViewImpl.java b/android/src/main/java/com/google/android/cameraview/CameraViewImpl.java new file mode 100644 index 0000000000..3a332969f3 --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/CameraViewImpl.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.media.CamcorderProfile; +import android.view.View; +import android.graphics.SurfaceTexture; + +import java.util.Set; + +abstract class CameraViewImpl { + + protected final Callback mCallback; + + protected final PreviewImpl mPreview; + + CameraViewImpl(Callback callback, PreviewImpl preview) { + mCallback = callback; + mPreview = preview; + } + + View getView() { + return mPreview.getView(); + } + + /** + * @return {@code true} if the implementation was able to start the camera session. + */ + abstract boolean start(); + + abstract void stop(); + + abstract boolean isCameraOpened(); + + abstract void setFacing(int facing); + + abstract int getFacing(); + + abstract Set getSupportedAspectRatios(); + + /** + * @return {@code true} if the aspect ratio was changed. + */ + abstract boolean setAspectRatio(AspectRatio ratio); + + abstract AspectRatio getAspectRatio(); + + abstract void setAutoFocus(boolean autoFocus); + + abstract boolean getAutoFocus(); + + abstract void setFlash(int flash); + + abstract int getFlash(); + + abstract void takePicture(); + + abstract boolean record(String path, int maxDuration, int maxFileSize, + boolean recordAudio, CamcorderProfile profile); + + abstract void stopRecording(); + + abstract void setDisplayOrientation(int displayOrientation); + + abstract void setFocusDepth(float value); + + abstract float getFocusDepth(); + + abstract void setZoom(float zoom); + + abstract float getZoom(); + + abstract void setWhiteBalance(int whiteBalance); + + abstract int getWhiteBalance(); + + abstract void setScanning(boolean isScanning); + + abstract boolean getScanning(); + + abstract public void setPreviewTexture(SurfaceTexture surfaceTexture); + + abstract public Size getPreviewSize(); + + interface Callback { + + void onCameraOpened(); + + void onCameraClosed(); + + void onPictureTaken(byte[] data); + + void onVideoRecorded(String path); + + void onFramePreview(byte[] data, int width, int height, int orientation); + + void onMountError(); + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/Constants.java b/android/src/main/java/com/google/android/cameraview/Constants.java new file mode 100644 index 0000000000..4fe1b87894 --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/Constants.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +public interface Constants { + + AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3); + + int FACING_BACK = 0; + int FACING_FRONT = 1; + + int FLASH_OFF = 0; + int FLASH_ON = 1; + int FLASH_TORCH = 2; + int FLASH_AUTO = 3; + int FLASH_RED_EYE = 4; + + int LANDSCAPE_90 = 90; + int LANDSCAPE_270 = 270; + + int WB_AUTO = 0; + int WB_CLOUDY = 1; + int WB_SUNNY = 2; + int WB_SHADOW = 3; + int WB_FLUORESCENT = 4; + int WB_INCANDESCENT = 5; + +} diff --git a/android/src/main/java/com/google/android/cameraview/DisplayOrientationDetector.java b/android/src/main/java/com/google/android/cameraview/DisplayOrientationDetector.java new file mode 100644 index 0000000000..14f5168557 --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/DisplayOrientationDetector.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.content.Context; +import android.util.SparseIntArray; +import android.view.Display; +import android.view.OrientationEventListener; +import android.view.Surface; + + +/** + * Monitors the value returned from {@link Display#getRotation()}. + */ +abstract class DisplayOrientationDetector { + + private final OrientationEventListener mOrientationEventListener; + + /** Mapping from Surface.Rotation_n to degrees. */ + static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); + + static { + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0); + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90); + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180); + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); + } + + Display mDisplay; + + private int mLastKnownDisplayOrientation = 0; + + public DisplayOrientationDetector(Context context) { + mOrientationEventListener = new OrientationEventListener(context) { + + /** This is either Surface.Rotation_0, _90, _180, _270, or -1 (invalid). */ + private int mLastKnownRotation = -1; + + @Override + public void onOrientationChanged(int orientation) { + if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN || + mDisplay == null) { + return; + } + final int rotation = mDisplay.getRotation(); + if (mLastKnownRotation != rotation) { + mLastKnownRotation = rotation; + dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation)); + } + } + }; + } + + public void enable(Display display) { + mDisplay = display; + mOrientationEventListener.enable(); + // Immediately dispatch the first callback + dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation())); + } + + public void disable() { + mOrientationEventListener.disable(); + mDisplay = null; + } + + public int getLastKnownDisplayOrientation() { + return mLastKnownDisplayOrientation; + } + + void dispatchOnDisplayOrientationChanged(int displayOrientation) { + mLastKnownDisplayOrientation = displayOrientation; + onDisplayOrientationChanged(displayOrientation); + } + + /** + * Called when display orientation is changed. + * + * @param displayOrientation One of 0, 90, 180, and 270. + */ + public abstract void onDisplayOrientationChanged(int displayOrientation); + +} diff --git a/android/src/main/java/com/google/android/cameraview/PreviewImpl.java b/android/src/main/java/com/google/android/cameraview/PreviewImpl.java new file mode 100644 index 0000000000..3b1e198d89 --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/PreviewImpl.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.View; + + +/** + * Encapsulates all the operations related to camera preview in a backward-compatible manner. + */ +abstract class PreviewImpl { + + interface Callback { + void onSurfaceChanged(); + + void onSurfaceDestroyed(); + } + + private Callback mCallback; + + private int mWidth; + + private int mHeight; + + void setCallback(Callback callback) { + mCallback = callback; + } + + abstract Surface getSurface(); + + abstract View getView(); + + abstract Class getOutputClass(); + + abstract void setDisplayOrientation(int displayOrientation); + + abstract boolean isReady(); + + protected void dispatchSurfaceChanged() { + mCallback.onSurfaceChanged(); + } + + protected void dispatchSurfaceDestroyed() { + mCallback.onSurfaceDestroyed(); + } + + SurfaceHolder getSurfaceHolder() { + return null; + } + + Object getSurfaceTexture() { + return null; + } + + void setBufferSize(int width, int height) { + } + + void setSize(int width, int height) { + mWidth = width; + mHeight = height; + } + + int getWidth() { + return mWidth; + } + + int getHeight() { + return mHeight; + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/Size.java b/android/src/main/java/com/google/android/cameraview/Size.java new file mode 100644 index 0000000000..d221bcbd3e --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/Size.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.support.annotation.NonNull; + +/** + * Immutable class for describing width and height dimensions in pixels. + */ +public class Size implements Comparable { + + private final int mWidth; + private final int mHeight; + + /** + * Create a new immutable Size instance. + * + * @param width The width of the size, in pixels + * @param height The height of the size, in pixels + */ + public Size(int width, int height) { + mWidth = width; + mHeight = height; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (this == o) { + return true; + } + if (o instanceof Size) { + Size size = (Size) o; + return mWidth == size.mWidth && mHeight == size.mHeight; + } + return false; + } + + @Override + public String toString() { + return mWidth + "x" + mHeight; + } + + @Override + public int hashCode() { + // assuming most sizes are <2^16, doing a rotate will give us perfect hashing + return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); + } + + @Override + public int compareTo(@NonNull Size another) { + return mWidth * mHeight - another.mWidth * another.mHeight; + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/SizeMap.java b/android/src/main/java/com/google/android/cameraview/SizeMap.java new file mode 100644 index 0000000000..b6772fbe93 --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/SizeMap.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.support.v4.util.ArrayMap; + +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s. + */ +class SizeMap { + + private final ArrayMap> mRatios = new ArrayMap<>(); + + /** + * Add a new {@link Size} to this collection. + * + * @param size The size to add. + * @return {@code true} if it is added, {@code false} if it already exists and is not added. + */ + public boolean add(Size size) { + for (AspectRatio ratio : mRatios.keySet()) { + if (ratio.matches(size)) { + final SortedSet sizes = mRatios.get(ratio); + if (sizes.contains(size)) { + return false; + } else { + sizes.add(size); + return true; + } + } + } + // None of the existing ratio matches the provided size; add a new key + SortedSet sizes = new TreeSet<>(); + sizes.add(size); + mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes); + return true; + } + + /** + * Removes the specified aspect ratio and all sizes associated with it. + * + * @param ratio The aspect ratio to be removed. + */ + public void remove(AspectRatio ratio) { + mRatios.remove(ratio); + } + + Set ratios() { + return mRatios.keySet(); + } + + SortedSet sizes(AspectRatio ratio) { + return mRatios.get(ratio); + } + + void clear() { + mRatios.clear(); + } + + boolean isEmpty() { + return mRatios.isEmpty(); + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/SurfaceViewPreview.java b/android/src/main/java/com/google/android/cameraview/SurfaceViewPreview.java new file mode 100644 index 0000000000..78872a5bae --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/SurfaceViewPreview.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.content.Context; +import android.support.v4.view.ViewCompat; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; + +import org.reactnative.camera.R; + +class SurfaceViewPreview extends PreviewImpl { + + final SurfaceView mSurfaceView; + + SurfaceViewPreview(Context context, ViewGroup parent) { + final View view = View.inflate(context, R.layout.surface_view, parent); + mSurfaceView = (SurfaceView) view.findViewById(R.id.surface_view); + final SurfaceHolder holder = mSurfaceView.getHolder(); + //noinspection deprecation + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + holder.addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(SurfaceHolder h) { + } + + @Override + public void surfaceChanged(SurfaceHolder h, int format, int width, int height) { + setSize(width, height); + if (!ViewCompat.isInLayout(mSurfaceView)) { + dispatchSurfaceChanged(); + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder h) { + setSize(0, 0); + } + }); + } + + @Override + Surface getSurface() { + return getSurfaceHolder().getSurface(); + } + + @Override + SurfaceHolder getSurfaceHolder() { + return mSurfaceView.getHolder(); + } + + @Override + View getView() { + return mSurfaceView; + } + + @Override + Class getOutputClass() { + return SurfaceHolder.class; + } + + @Override + void setDisplayOrientation(int displayOrientation) { + } + + @Override + boolean isReady() { + return getWidth() != 0 && getHeight() != 0; + } + +} diff --git a/android/src/main/java/com/google/android/cameraview/TextureViewPreview.java b/android/src/main/java/com/google/android/cameraview/TextureViewPreview.java new file mode 100644 index 0000000000..7215ed369f --- /dev/null +++ b/android/src/main/java/com/google/android/cameraview/TextureViewPreview.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.SurfaceTexture; +import android.view.Surface; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; + +import org.reactnative.camera.R; + +@TargetApi(14) +class TextureViewPreview extends PreviewImpl { + + private final TextureView mTextureView; + + private int mDisplayOrientation; + + TextureViewPreview(Context context, ViewGroup parent) { + final View view = View.inflate(context, R.layout.texture_view, parent); + mTextureView = (TextureView) view.findViewById(R.id.texture_view); + mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + setSize(width, height); + configureTransform(); + dispatchSurfaceChanged(); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + setSize(width, height); + configureTransform(); + dispatchSurfaceChanged(); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + setSize(0, 0); + dispatchSurfaceDestroyed(); + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } + }); + } + + // This method is called only from Camera2. + @TargetApi(15) + @Override + void setBufferSize(int width, int height) { + mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); + } + + @Override + Surface getSurface() { + return new Surface(mTextureView.getSurfaceTexture()); + } + + @Override + SurfaceTexture getSurfaceTexture() { + return mTextureView.getSurfaceTexture(); + } + + @Override + View getView() { + return mTextureView; + } + + @Override + Class getOutputClass() { + return SurfaceTexture.class; + } + + @Override + void setDisplayOrientation(int displayOrientation) { + mDisplayOrientation = displayOrientation; + configureTransform(); + } + + @Override + boolean isReady() { + return mTextureView.getSurfaceTexture() != null; + } + + /** + * Configures the transform matrix for TextureView based on {@link #mDisplayOrientation} and + * the surface size. + */ + void configureTransform() { + Matrix matrix = new Matrix(); + if (mDisplayOrientation % 180 == 90) { + final int width = getWidth(); + final int height = getHeight(); + // Rotate the camera preview when the screen is landscape. + matrix.setPolyToPoly( + new float[]{ + 0.f, 0.f, // top left + width, 0.f, // top right + 0.f, height, // bottom left + width, height, // bottom right + }, 0, + mDisplayOrientation == 90 ? + // Clockwise + new float[]{ + 0.f, height, // top left + 0.f, 0.f, // top right + width, height, // bottom left + width, 0.f, // bottom right + } : // mDisplayOrientation == 270 + // Counter-clockwise + new float[]{ + width, 0.f, // top left + width, height, // top right + 0.f, 0.f, // bottom left + 0.f, height, // bottom right + }, 0, + 4); + } else if (mDisplayOrientation == 180) { + matrix.postRotate(180, getWidth() / 2, getHeight() / 2); + } + mTextureView.setTransform(matrix); + } + +} diff --git a/android/src/main/res/layout-v14/texture_view.xml b/android/src/main/res/layout-v14/texture_view.xml new file mode 100644 index 0000000000..91953b7e84 --- /dev/null +++ b/android/src/main/res/layout-v14/texture_view.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/android/src/main/res/layout/surface_view.xml b/android/src/main/res/layout/surface_view.xml new file mode 100644 index 0000000000..4029bb9141 --- /dev/null +++ b/android/src/main/res/layout/surface_view.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/android/src/main/res/values/attrs.xml b/android/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..7ac245d8b1 --- /dev/null +++ b/android/src/main/res/values/attrs.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/values/public.xml b/android/src/main/res/values/public.xml new file mode 100644 index 0000000000..84966fa680 --- /dev/null +++ b/android/src/main/res/values/public.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml new file mode 100644 index 0000000000..6ded716840 --- /dev/null +++ b/android/src/main/res/values/styles.xml @@ -0,0 +1,24 @@ + + + + + + + From 564ead0228089c7c8e4bf4a221884ff0be08ccfe Mon Sep 17 00:00:00 2001 From: Jason Brown Date: Thu, 19 Apr 2018 17:17:32 +0100 Subject: [PATCH 03/23] FIXED warning for focusDepth on front-facing camera --- ios/RN/RNCamera.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RN/RNCamera.m b/ios/RN/RNCamera.m index a0037ebd70..a5e9d6bd36 100644 --- a/ios/RN/RNCamera.m +++ b/ios/RN/RNCamera.m @@ -210,7 +210,7 @@ - (void)updateFocusDepth AVCaptureDevice *device = [self.videoCaptureDeviceInput device]; NSError *error = nil; - if (self.autoFocus < 0 || device.focusMode != RNCameraAutoFocusOff) { + if (self.autoFocus < 0 || device.focusMode != RNCameraAutoFocusOff || device.position == RNCameraTypeFront) { return; } From 4f6b213dc63e7ae96c77a1cf1627c14fcda99a94 Mon Sep 17 00:00:00 2001 From: Sibelius Seraphini Date: Wed, 25 Apr 2018 09:07:05 -0300 Subject: [PATCH 04/23] build(change-log): v1.1.2-4 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce023efd39..8db4a8b880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +#### 1.1.2-4 (2018-04-25) + +##### Chores + +* **cameraview:** integrate google's cameraview directly on rncamera? ([d11ed319](https://github.com/react-native-community/react-native-camera/commit/d11ed31917c26df151b4fb46ab166d2921a9ac99)) + +##### Bug Fixes + +* **search-paths:** remove unnecessary search paths and add missing one ([dee298b4](https://github.com/react-native-community/react-native-camera/commit/dee298b4fefca4659468fd43e914fd1c970ca930)) + #### 1.1.1-3 (2018-04-15) ##### Build System / Dependencies From b1721b873e8d32709b4adab41aa8f099b65b2116 Mon Sep 17 00:00:00 2001 From: Sibelius Seraphini Date: Wed, 25 Apr 2018 09:07:09 -0300 Subject: [PATCH 05/23] 1.1.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9b0c44686..e60bf6b60b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-native-camera", - "version": "1.1.0", + "version": "1.1.2", "lockfileVersion": 1, "dependencies": { "ansi-escapes": { diff --git a/package.json b/package.json index c249c06dc5..595512dcfa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-camera", "description": "A Camera component for React Native. Also reads barcodes.", - "version": "1.1.1", + "version": "1.1.2", "author": "Lochlan Wansbrough (http://lwansbrough.com)", "collective": { "type": "opencollective", From 45cc8f25d2de71b9eee29e1fe14e2f4f3d2feee9 Mon Sep 17 00:00:00 2001 From: AndreiCalazans Date: Wed, 25 Apr 2018 18:10:00 -0300 Subject: [PATCH 06/23] feat(rn_camera): add function as children --- docs/RNCamera.md | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ src/RNCamera.js | 23 +++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/docs/RNCamera.md b/docs/RNCamera.md index 10355dd0de..35af236bbd 100644 --- a/docs/RNCamera.md +++ b/docs/RNCamera.md @@ -78,6 +78,101 @@ const styles = StyleSheet.create({ AppRegistry.registerComponent('BadInstagramCloneApp', () => BadInstagramCloneApp); ``` +## FaCC (Function as Child Components) + +**You can also use it with Facc.* + +```javascript + 'use strict'; +import React, { Component } from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { RNCamera } from 'react-native-camera'; + +const PendingView = () => ( + + Waiting + +); + +class App extends Component { + render() { + return ( + + { + this.camera = ref; + }} + style={styles.preview} + type={RNCamera.Constants.Type.back} + flashMode={RNCamera.Constants.FlashMode.on} + permissionDialogTitle={'Permission to use camera'} + permissionDialogMessage={'We need your permission to use your camera phone'} + > + {({ camera, status }) => { + if (status !== 'READY') return ; + return ( + + this.takePicture(camera)} style={styles.capture}> + SNAP + + + ); + }} + + + ); + } + + takePicture = async function(camera) { + const options = { quality: 0.5, base64: true }; + const data = await camera.takePictureAsync(options); + // eslint-disable-next-line + console.log(data.uri); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column', + backgroundColor: 'black', + }, + preview: { + flex: 1, + justifyContent: 'flex-end', + alignItems: 'center', + }, + capture: { + flex: 0, + backgroundColor: '#fff', + borderRadius: 5, + padding: 15, + paddingHorizontal: 20, + alignSelf: 'center', + margin: 20, + }, +}); + +AppRegistry.registerComponent('BadInstagramCloneApp', () => App); +``` + +#### `camera` + +*It's the RNCamera's reference* + +#### `status` + +'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED' + + + ## Properties #### `autoFocus` diff --git a/src/RNCamera.js b/src/RNCamera.js index 31e26e6293..d178780e1c 100644 --- a/src/RNCamera.js +++ b/src/RNCamera.js @@ -100,6 +100,8 @@ type StateType = { isAuthorizationChecked: boolean, }; +type Status = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED'; + const CameraManager: Object = NativeModules.RNCameraManager || NativeModules.RNCameraModule || { stubbed: true, @@ -314,10 +316,25 @@ export default class Camera extends React.Component { this.setState({ isAuthorized, isAuthorizationChecked: true }); } + getStatus = (): Status => { + const { isAuthorized, isAuthorizationChecked } = this.state; + return isAuthorized ? 'READY' : isAuthorizationChecked ? 'PENDING_AUTHORIZATION' : 'NOT_AUTHORIZED'; + } + + // CAF = Children as Function; + hasCAF = (): * => typeof this.props.children === 'function'; + + renderChildren = (): * => { + if (this.hasCAF()) { + return this.props.children({ camera: this, status: this.getStatus() }) + } + return null; + } + render() { const nativeProps = this._convertNativeProps(this.props); - if (this.state.isAuthorized) { + if (this.state.isAuthorized || this.hasCAF()) { return ( { onBarCodeRead={this._onObjectDetected(this.props.onBarCodeRead)} onFacesDetected={this._onObjectDetected(this.props.onFacesDetected)} onTextRecognized={this._onObjectDetected(this.props.onTextRecognized)} - /> + > + {this.renderChildren()} + ); } else if (!this.state.isAuthorizationChecked) { return this.props.pendingAuthorizationView; From 3811d82c75ceedc27b8aa5550e352159d5daf2b8 Mon Sep 17 00:00:00 2001 From: AndreiCalazans Date: Wed, 25 Apr 2018 18:12:53 -0300 Subject: [PATCH 07/23] fix(rn_camera): improve naming --- docs/RNCamera.md | 3 --- src/RNCamera.js | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/RNCamera.md b/docs/RNCamera.md index 35af236bbd..7147e3f99a 100644 --- a/docs/RNCamera.md +++ b/docs/RNCamera.md @@ -106,9 +106,6 @@ class App extends Component { return ( { - this.camera = ref; - }} style={styles.preview} type={RNCamera.Constants.Type.back} flashMode={RNCamera.Constants.FlashMode.on} diff --git a/src/RNCamera.js b/src/RNCamera.js index d178780e1c..37e8361014 100644 --- a/src/RNCamera.js +++ b/src/RNCamera.js @@ -321,11 +321,11 @@ export default class Camera extends React.Component { return isAuthorized ? 'READY' : isAuthorizationChecked ? 'PENDING_AUTHORIZATION' : 'NOT_AUTHORIZED'; } - // CAF = Children as Function; - hasCAF = (): * => typeof this.props.children === 'function'; + // FaCC = Function as Child Component; + hasFaCC = (): * => typeof this.props.children === 'function'; renderChildren = (): * => { - if (this.hasCAF()) { + if (this.hasFaCC()) { return this.props.children({ camera: this, status: this.getStatus() }) } return null; @@ -334,7 +334,7 @@ export default class Camera extends React.Component { render() { const nativeProps = this._convertNativeProps(this.props); - if (this.state.isAuthorized || this.hasCAF()) { + if (this.state.isAuthorized || this.hasFaCC()) { return ( Date: Wed, 25 Apr 2018 19:43:56 -0500 Subject: [PATCH 08/23] fix(cache): store video recordings in same directory as photos --- ios/RN/RNCamera.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RN/RNCamera.m b/ios/RN/RNCamera.m index a5e9d6bd36..3edabb3f31 100644 --- a/ios/RN/RNCamera.m +++ b/ios/RN/RNCamera.m @@ -428,7 +428,7 @@ - (void)record:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve r dispatch_async(self.sessionQueue, ^{ [self updateFlashMode]; - NSString *path = [RNFileSystem generatePathInDirectory:[[RNFileSystem cacheDirectoryPath] stringByAppendingString:@"Camera"] withExtension:@".mov"]; + NSString *path = [RNFileSystem generatePathInDirectory:[[RNFileSystem cacheDirectoryPath] stringByAppendingPathComponent:@"Camera"] withExtension:@".mov"]; NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path]; [self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self]; self.videoRecordedResolve = resolve; From f01ba34aafde45bd18279a74ca123580ea382535 Mon Sep 17 00:00:00 2001 From: Victor Powell Date: Wed, 25 Apr 2018 22:08:06 -0700 Subject: [PATCH 09/23] Add missing infer annotation dependency. --- android/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/android/build.gradle b/android/build.gradle index 6a7219c901..f7ba1dd54a 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -54,6 +54,7 @@ dependencies { def supportLibVersion = rootProject.hasProperty('supportLibVersion') ? rootProject.supportLibVersion : DEFAULT_SUPPORT_LIBRARY_VERSION compileOnly 'com.facebook.react:react-native:+' + compileOnly 'com.facebook.infer.annotation:infer-annotation:+' implementation "com.google.zxing:core:3.2.1" implementation "com.drewnoakes:metadata-extractor:2.9.1" implementation "com.google.android.gms:play-services-vision:$googlePlayServicesVersion" From 842dc1cb581bd28653549dee86f70c2ff5d65ee2 Mon Sep 17 00:00:00 2001 From: Felipe Constantino Date: Thu, 26 Apr 2018 11:13:02 -0300 Subject: [PATCH 10/23] feat(types): add types for #1518 (FaCC) --- types/index.d.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 3be34999f7..12b3d96bb3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -9,7 +9,7 @@ * * If you are seeing this from the future, please, send us your cutting-edge technology :) (if it exists) */ -import { Component } from 'react'; +import { Component, ReactNode } from 'react'; import { ViewProperties } from "react-native"; type AutoFocus = { on: any, off: any }; @@ -23,8 +23,17 @@ type VideoCodec = { 'H264': symbol, 'JPEG': symbol, 'HVEC': symbol, 'AppleProRes type FaceDetectionClassifications = { all: any, none: any }; type FaceDetectionLandmarks = { all: any, none: any }; type FaceDetectionMode = { fast: any, accurate: any }; -type GoogleVisionBarcodeType = { CODE_128: any, CODE_39: any, CODABAR: any, DATA_MATRIX: any, EAN_13: any, EAN_8: any, ITF: any, - QR_CODE: any, UPC_A: any, UPC_E: any, PDF417: any, AZTEC: any } +type GoogleVisionBarcodeType = { + CODE_128: any, CODE_39: any, CODABAR: any, DATA_MATRIX: any, EAN_13: any, EAN_8: any, + ITF: any, QR_CODE: any, UPC_A: any, UPC_E: any, PDF417: any, AZTEC: any +}; + +// FaCC (Function as Child Components) +type CameraStatus = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED'; +type FaCC = (params: { + camera: RNCamera, + status: CameraStatus +}) => JSX.Element; export interface Constants { AutoFocus: AutoFocus; @@ -45,6 +54,8 @@ export interface Constants { } export interface RNCameraProps { + children?: ReactNode | FaCC; + autoFocus?: keyof AutoFocus; type?: keyof CameraType; flashMode?: keyof FlashMode; From f61004de623a2011e99a6a8092048b513025f5ed Mon Sep 17 00:00:00 2001 From: Felipe Constantino Date: Thu, 26 Apr 2018 14:03:20 -0300 Subject: [PATCH 11/23] feat(types): add types for #1523 --- types/index.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index 12b3d96bb3..6a2d4f2af7 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -17,7 +17,11 @@ type FlashMode = { on: any, off: any, torch: any, auto: any }; type CameraType = { front: any, back: any }; type WhiteBalance = { sunny: any, cloudy: any, shadow: any, incandescent: any, fluorescent: any, auto: any }; type BarCodeType = { aztec: any, code128: any, code39: any, code39mod43: any, code93: any, ean13: any, ean8: any, pdf417: any, qr: any, upce: any, interleaved2of5: any, itf14: any, datamatrix: any }; -type VideoQuality = { '2160p': any, '1080p': any, '720p': any, '480p': any, '4:3': any }; +type VideoQuality = { + '2160p': any, '1080p': any, '720p': any, '480p': any, '4:3': any; + /** iOS Only. Android not supported. */ + '288p': any; +}; type VideoCodec = { 'H264': symbol, 'JPEG': symbol, 'HVEC': symbol, 'AppleProRes422': symbol, 'AppleProRes4444': symbol }; type FaceDetectionClassifications = { all: any, none: any }; From bf22df151769706f2adcffd27c9a26198332a428 Mon Sep 17 00:00:00 2001 From: Sibelius Seraphini Date: Thu, 26 Apr 2018 17:43:02 -0300 Subject: [PATCH 12/23] add gradle 3 on issue template --- .github/ISSUE_TEMPLATE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 1e0a6be661..78d9568402 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,9 @@ ### Warning RCTCamera is **DEPRECATED** on v1.0.0 follow our migration guide here https://github.com/react-native-community/react-native-camera/blob/master/docs/migration.md +### Gradle 3 +This package required Gradle 3, follow this migration guide https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration + ### Which implementation are you using *RNCamera* or RCTCamera (RCTCamera will be removed on v2.0.0)? From 6b3bbe1e05ce42d8862c1b5eb935d372e05f3a11 Mon Sep 17 00:00:00 2001 From: Thomas Parslow Date: Thu, 3 May 2018 18:59:53 +0100 Subject: [PATCH 13/23] The correct capture preset for photos is AVCaptureSessionPresetPhoto AVCaptureSessionPresetHigh sometimes means the camera is too zoomed in --- ios/RN/RNCamera.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RN/RNCamera.m b/ios/RN/RNCamera.m index 3edabb3f31..47179d7f75 100644 --- a/ios/RN/RNCamera.m +++ b/ios/RN/RNCamera.m @@ -776,8 +776,8 @@ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToO [_faceDetectorManager maybeStartFaceDetectionOnSession:_session withPreviewLayer:_previewLayer]; #endif - if (self.session.sessionPreset != AVCaptureSessionPresetHigh) { - [self updateSessionPreset:AVCaptureSessionPresetHigh]; + if (self.session.sessionPreset != AVCaptureSessionPresetPhoto) { + [self updateSessionPreset:AVCaptureSessionPresetPhoto]; } } From 9ba5a2376c48dfa128c32a977ec1f89217efe3b7 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Mon, 7 May 2018 14:14:24 +0200 Subject: [PATCH 14/23] fix status for non authorized/pending camera --- src/RNCamera.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RNCamera.js b/src/RNCamera.js index 37e8361014..e8d14b83c8 100644 --- a/src/RNCamera.js +++ b/src/RNCamera.js @@ -318,7 +318,7 @@ export default class Camera extends React.Component { getStatus = (): Status => { const { isAuthorized, isAuthorizationChecked } = this.state; - return isAuthorized ? 'READY' : isAuthorizationChecked ? 'PENDING_AUTHORIZATION' : 'NOT_AUTHORIZED'; + return isAuthorized ? 'READY' : isAuthorizationChecked ? 'NOT_AUTHORIZED' : 'PENDING_AUTHORIZATION'; } // FaCC = Function as Child Component; From c8c6fdea0bf15de60c638f504f38dcb9ac80a3e4 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Mon, 7 May 2018 14:28:10 +0200 Subject: [PATCH 15/23] feat(rn-camera): use and export constants --- src/RNCamera.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/RNCamera.js b/src/RNCamera.js index e8d14b83c8..7a83a3745f 100644 --- a/src/RNCamera.js +++ b/src/RNCamera.js @@ -102,6 +102,12 @@ type StateType = { type Status = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED'; +const CameraStatus = { + READY: 'READY', + PENDING_AUTHORIZATION: 'PENDING_AUTHORIZATION', + NOT_AUTHORIZED: 'NOT_AUTHORIZED', +}; + const CameraManager: Object = NativeModules.RNCameraManager || NativeModules.RNCameraModule || { stubbed: true, @@ -144,6 +150,7 @@ export default class Camera extends React.Component { BarCodeType: CameraManager.BarCodeType, GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection, FaceDetection: CameraManager.FaceDetection, + CameraStatus, }; // Values under keys from this object will be transformed to native options @@ -318,18 +325,21 @@ export default class Camera extends React.Component { getStatus = (): Status => { const { isAuthorized, isAuthorizationChecked } = this.state; - return isAuthorized ? 'READY' : isAuthorizationChecked ? 'NOT_AUTHORIZED' : 'PENDING_AUTHORIZATION'; - } + if (isAuthorizationChecked) { + return CameraStatus.PENDING_AUTHORIZATION; + } + return isAuthorized ? CameraStatus.READY : CameraStatus.NOT_AUTHORIZED; + }; // FaCC = Function as Child Component; hasFaCC = (): * => typeof this.props.children === 'function'; renderChildren = (): * => { if (this.hasFaCC()) { - return this.props.children({ camera: this, status: this.getStatus() }) + return this.props.children({ camera: this, status: this.getStatus() }); } return null; - } + }; render() { const nativeProps = this._convertNativeProps(this.props); @@ -348,7 +358,7 @@ export default class Camera extends React.Component { onFacesDetected={this._onObjectDetected(this.props.onFacesDetected)} onTextRecognized={this._onObjectDetected(this.props.onTextRecognized)} > - {this.renderChildren()} + {this.renderChildren()} ); } else if (!this.state.isAuthorizationChecked) { From 858cc4c9c8fd456390b274ee4cfddb62fee198ee Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Mon, 7 May 2018 14:45:04 +0200 Subject: [PATCH 16/23] fix(rn-camera): inject correct status --- src/RNCamera.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RNCamera.js b/src/RNCamera.js index 7a83a3745f..5f209a697e 100644 --- a/src/RNCamera.js +++ b/src/RNCamera.js @@ -325,7 +325,7 @@ export default class Camera extends React.Component { getStatus = (): Status => { const { isAuthorized, isAuthorizationChecked } = this.state; - if (isAuthorizationChecked) { + if (isAuthorizationChecked === false) { return CameraStatus.PENDING_AUTHORIZATION; } return isAuthorized ? CameraStatus.READY : CameraStatus.NOT_AUTHORIZED; From 3ce3c80db670cc05dead7636d70dc8fc911a2c6b Mon Sep 17 00:00:00 2001 From: Felipe Cavalcante Constantino Date: Mon, 7 May 2018 13:33:58 -0300 Subject: [PATCH 17/23] feat(types): add types for #1547 (#1548) --- types/index.d.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 6a2d4f2af7..9728ff7fd6 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -12,34 +12,36 @@ import { Component, ReactNode } from 'react'; import { ViewProperties } from "react-native"; -type AutoFocus = { on: any, off: any }; -type FlashMode = { on: any, off: any, torch: any, auto: any }; -type CameraType = { front: any, back: any }; -type WhiteBalance = { sunny: any, cloudy: any, shadow: any, incandescent: any, fluorescent: any, auto: any }; -type BarCodeType = { aztec: any, code128: any, code39: any, code39mod43: any, code93: any, ean13: any, ean8: any, pdf417: any, qr: any, upce: any, interleaved2of5: any, itf14: any, datamatrix: any }; -type VideoQuality = { +type AutoFocus = Readonly<{ on: any, off: any }>; +type FlashMode = Readonly<{ on: any, off: any, torch: any, auto: any }>; +type CameraType = Readonly<{ front: any, back: any }>; +type WhiteBalance = Readonly<{ sunny: any, cloudy: any, shadow: any, incandescent: any, fluorescent: any, auto: any }>; +type BarCodeType = Readonly<{ aztec: any, code128: any, code39: any, code39mod43: any, code93: any, ean13: any, ean8: any, pdf417: any, qr: any, upce: any, interleaved2of5: any, itf14: any, datamatrix: any }>; +type VideoQuality = Readonly<{ '2160p': any, '1080p': any, '720p': any, '480p': any, '4:3': any; /** iOS Only. Android not supported. */ '288p': any; -}; -type VideoCodec = { 'H264': symbol, 'JPEG': symbol, 'HVEC': symbol, 'AppleProRes422': symbol, 'AppleProRes4444': symbol }; +}>; +type VideoCodec = Readonly<{ 'H264': symbol, 'JPEG': symbol, 'HVEC': symbol, 'AppleProRes422': symbol, 'AppleProRes4444': symbol }>; -type FaceDetectionClassifications = { all: any, none: any }; -type FaceDetectionLandmarks = { all: any, none: any }; -type FaceDetectionMode = { fast: any, accurate: any }; -type GoogleVisionBarcodeType = { +type FaceDetectionClassifications = Readonly<{ all: any, none: any }>; +type FaceDetectionLandmarks = Readonly<{ all: any, none: any }>; +type FaceDetectionMode = Readonly<{ fast: any, accurate: any }>; +type GoogleVisionBarcodeType = Readonly<{ CODE_128: any, CODE_39: any, CODABAR: any, DATA_MATRIX: any, EAN_13: any, EAN_8: any, ITF: any, QR_CODE: any, UPC_A: any, UPC_E: any, PDF417: any, AZTEC: any -}; +}>; // FaCC (Function as Child Components) -type CameraStatus = 'READY' | 'PENDING_AUTHORIZATION' | 'NOT_AUTHORIZED'; +type Self = { [P in keyof T]: P } +type CameraStatus = Readonly>; type FaCC = (params: { camera: RNCamera, - status: CameraStatus + status: keyof CameraStatus }) => JSX.Element; export interface Constants { + CameraStatus: CameraStatus; AutoFocus: AutoFocus; FlashMode: FlashMode; VideoCodec: VideoCodec; From 9472f550685a9c348d2ba175015998cf5db4c3a1 Mon Sep 17 00:00:00 2001 From: Jason Brown Date: Wed, 9 May 2018 11:27:50 +0100 Subject: [PATCH 18/23] UPDATED renderChildren to return this.props.children instead of null --- src/RNCamera.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RNCamera.js b/src/RNCamera.js index 5f209a697e..793bb812b8 100644 --- a/src/RNCamera.js +++ b/src/RNCamera.js @@ -338,7 +338,7 @@ export default class Camera extends React.Component { if (this.hasFaCC()) { return this.props.children({ camera: this, status: this.getStatus() }); } - return null; + return this.props.children; }; render() { From 09f11ce7c43a7b830162dd56f98533cd5cba0185 Mon Sep 17 00:00:00 2001 From: Karen Date: Mon, 14 May 2018 22:14:06 -0700 Subject: [PATCH 19/23] Fix run time error missing facebook infer annotation --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index f7ba1dd54a..810192194c 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -54,7 +54,7 @@ dependencies { def supportLibVersion = rootProject.hasProperty('supportLibVersion') ? rootProject.supportLibVersion : DEFAULT_SUPPORT_LIBRARY_VERSION compileOnly 'com.facebook.react:react-native:+' - compileOnly 'com.facebook.infer.annotation:infer-annotation:+' + implementation 'com.facebook.infer.annotation:infer-annotation:+' implementation "com.google.zxing:core:3.2.1" implementation "com.drewnoakes:metadata-extractor:2.9.1" implementation "com.google.android.gms:play-services-vision:$googlePlayServicesVersion" From 6ce014d3ca3805f908fbdcd30da9b982de3bc2da Mon Sep 17 00:00:00 2001 From: Ville Ahti Date: Thu, 17 May 2018 14:32:55 +0300 Subject: [PATCH 20/23] Android: Fix java.lang.ArrayIndexOutOfBoundsException with image rotation --- .../src/main/java/org/reactnative/camera/RNCameraView.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/org/reactnative/camera/RNCameraView.java b/android/src/main/java/org/reactnative/camera/RNCameraView.java index c3ee6b47e3..d633eecc4f 100644 --- a/android/src/main/java/org/reactnative/camera/RNCameraView.java +++ b/android/src/main/java/org/reactnative/camera/RNCameraView.java @@ -108,7 +108,11 @@ private byte[] rotateImage(byte[] imageData, int height, int width) { byte[] rotated = new byte[imageData.length]; for (int y = 0; y < width; y++) { for (int x = 0; x < height; x++) { - rotated[x * width + width - y - 1] = imageData[x + y * height]; + int sourceIx = x + y * height; + int destIx = x * width + width - y - 1; + if (sourceIx >= 0 && sourceIx < imageData.length && destIx >= 0 && destIx < imageData.length) { + rotated[destIx] = imageData[sourceIx]; + } } } return rotated; From bd69ea399665f606f49b4bfbb2d78b8852428bdc Mon Sep 17 00:00:00 2001 From: Jack Zhao Date: Thu, 17 May 2018 22:08:36 +0100 Subject: [PATCH 21/23] Create GradleUpgradeGuide.md --- docs/GradleUpgradeGuide.md | 102 +++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 docs/GradleUpgradeGuide.md diff --git a/docs/GradleUpgradeGuide.md b/docs/GradleUpgradeGuide.md new file mode 100644 index 0000000000..cc4dafc532 --- /dev/null +++ b/docs/GradleUpgradeGuide.md @@ -0,0 +1,102 @@ +# Upgrade gradle for Android projects + +To integrate react-native-camera into your own react native project and make it work for Android, you need to edit the following files in the `android` folder under your project folder: + +* In the `android/gradle.properties` file: + +``` +android.useDeprecatedNdk=true +android.enableAapt2=false +``` + + +* In the `android/build.gradle` file: + +``` +uildscript { + repositories { + jcenter() + maven { + url 'https://maven.google.com' + name 'Google' + } + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenLocal() + jcenter() + maven { + url 'https://maven.google.com' + name 'Google' + } + maven { url "https://jitpack.io" } + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + configurations.all { + resolutionStrategy { + force 'com.facebook.android:facebook-android-sdk:4.28.0' + } + } + } +} + +subprojects { + project.configurations.all { + resolutionStrategy.eachDependency { details -> + if (details.requested.group == 'com.android.support' + && !details.requested.name.contains('multidex') ) { + details.useVersion "26.0.1" + } + } + } +} +``` + +* In the `android/app/build.gradle` file: + +``` +android { + compileSdkVersion 26 + buildToolsVersion "26.0.1" + + defaultConfig { + applicationId "appName" + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" + } + } + +... + +dependencies { + implementation project(':react-native-camera') + ... + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation 'com.android.support:appcompat-v7:26.0.1' + implementation "com.facebook.react:react-native:+" // From node_modules +} +``` + +* In the `android/gradle/gradle-wrapper.properties` file: + +``` +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +``` From 269418c126d6446b63c7ccef05f8bbfaf11d7f9f Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 18 May 2018 15:59:03 +0300 Subject: [PATCH 22/23] reenable camera on android emulator --- .../org/reactnative/camera/CameraModule.java | 17 +++++------------ .../org/reactnative/camera/RNCameraView.java | 4 +--- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/org/reactnative/camera/CameraModule.java b/android/src/main/java/org/reactnative/camera/CameraModule.java index 610db105c2..99aa93af72 100644 --- a/android/src/main/java/org/reactnative/camera/CameraModule.java +++ b/android/src/main/java/org/reactnative/camera/CameraModule.java @@ -190,18 +190,11 @@ public void takePicture(final ReadableMap options, final int viewTag, final Prom public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { RNCameraView cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag); try { - if (!Build.FINGERPRINT.contains("generic")) { - if (cameraView.isCameraOpened()) { - cameraView.takePicture(options, promise, cacheDirectory); - } else { - promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running"); - } - } else { - Bitmap image = RNCameraViewHelper.generateSimulatorPhoto(cameraView.getWidth(), cameraView.getHeight()); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - image.compress(Bitmap.CompressFormat.JPEG, 100, stream); - new ResolveTakenPictureAsyncTask(stream.toByteArray(), promise, options, cacheDirectory).execute(); - } + if (cameraView.isCameraOpened()) { + cameraView.takePicture(options, promise, cacheDirectory); + } else { + promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running"); + } } catch (Exception e) { promise.reject("E_CAMERA_BAD_VIEWTAG", "takePictureAsync: Expected a Camera component"); } diff --git a/android/src/main/java/org/reactnative/camera/RNCameraView.java b/android/src/main/java/org/reactnative/camera/RNCameraView.java index d633eecc4f..764b2d4469 100644 --- a/android/src/main/java/org/reactnative/camera/RNCameraView.java +++ b/android/src/main/java/org/reactnative/camera/RNCameraView.java @@ -442,9 +442,7 @@ public void onHostResume() { if ((mIsPaused && !isCameraOpened()) || mIsNew) { mIsPaused = false; mIsNew = false; - if (!Build.FINGERPRINT.contains("generic")) { - start(); - } + start(); } } else { RNCameraViewHelper.emitMountErrorEvent(this, "Camera permissions not granted - component could not be rendered."); From 685bead0e5ba00b373890d57b46b1c1dc340870a Mon Sep 17 00:00:00 2001 From: davidpricedev Date: Fri, 18 May 2018 12:10:29 -0500 Subject: [PATCH 23/23] Updates Pod Install Instructions --- README.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f86d9d6bce..39a96516d2 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,8 @@ And add something like this to the `scripts` section in your `package.json`: ##### Installing GMV frameworks GMV (Google Mobile Vision) is used for Face detection by the iOS RNCamera. You have to link the google frameworks to your project to successfully compile the RNCamera project. -1. If using **CocoaPods** modify the dependency towards `react-native-camera` in your +###### CocoaPods Path +1. Modify the dependency towards `react-native-camera` in your `Podfile`, from ``` @@ -128,8 +129,17 @@ to pod 'react-native-camera', subspecs: ['RCT', 'RN', 'FaceDetector'], path: '../node_modules/react-native-camera' ``` +2. Add the following to your `Podfile`: +``` + pod 'GoogleMobileVision/Detector', '~> 1.1.0' + pod 'GoogleMobileVision/MVDataOutput', '~> 1.1.0' + pod 'GoogleMobileVision/FaceDetector', '~> 1.1.0' +``` + +3. In XCode, On your target -> Build Phases -> Link Binary with Libraries -> add AddressBook.framework -2. Download: +###### Non-CocoaPods Path +1. Download: Google Symbol Utilities: https://www.gstatic.com/cpdc/dbffca986f6337f8-GoogleSymbolUtilities-1.1.1.tar.gz Google Utilities: https://dl.google.com/dl/cpdc/978f81964b50a7c0/GoogleUtilities-1.3.2.tar.gz @@ -140,13 +150,13 @@ Google Symbol Utilities: https://www.gstatic.com/cpdc/dbffca986f6337f8-GoogleSym Google Interchange Utilities: https://dl.google.com/dl/cpdc/1a7f7ba905b2c029/GoogleInterchangeUtilities-1.2.2.tar.gz -3. Extract everything to one folder. Delete "BarcodeDetector" and "copy" folders from Google Mobile Vision. +2. Extract everything to one folder. Delete "BarcodeDetector" and "copy" folders from Google Mobile Vision. -4. Open XCode, right click on your project and choose "New Group". Rename the new folder to "Frameworks". Right click on "Frameworks" and select "add files to 'YOUR_PROJECT'". Select all content from the folder of step 2, click on Options. Select "Copy items if needed", leave "Create groups" selected and choose all your targets on the "Add to targets" section. Then, click on "Add". +3. Open XCode, right click on your project and choose "New Group". Rename the new folder to "Frameworks". Right click on "Frameworks" and select "add files to 'YOUR_PROJECT'". Select all content from the folder of step 2, click on Options. Select "Copy items if needed", leave "Create groups" selected and choose all your targets on the "Add to targets" section. Then, click on "Add". -5. On your target -> Build Phases -> Link Binary with Libraries -> add AddressBook.framework -6. On your target -> Build Settings -> Other Linker Flags -> add -lz, -ObjC and -lc++ -7. To force indexing and prevent errors, restart xcode and reopen your project again before compiling. +4. On your target -> Build Phases -> Link Binary with Libraries -> add AddressBook.framework +5. On your target -> Build Settings -> Other Linker Flags -> add -lz, -ObjC and -lc++ +6. To force indexing and prevent errors, restart xcode and reopen your project again before compiling. #### Android 1. `npm install react-native-camera --save`