Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			031aa9d43a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 031aa9d43a | 
| @@ -133,8 +133,8 @@ android { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   compileOptions { |   compileOptions { | ||||||
|     sourceCompatibility JavaVersion.VERSION_1_8 |     sourceCompatibility JavaVersion.VERSION_17 | ||||||
|     targetCompatibility JavaVersion.VERSION_1_8 |     targetCompatibility JavaVersion.VERSION_17 | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   externalNativeBuild { |   externalNativeBuild { | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|           package="com.mrousavy.camera"> |  | ||||||
|  |  | ||||||
| </manifest> | </manifest> | ||||||
|   | |||||||
| @@ -13,36 +13,69 @@ import com.facebook.react.bridge.ReadableMap | |||||||
| import com.facebook.react.bridge.WritableMap | import com.facebook.react.bridge.WritableMap | ||||||
| import com.mrousavy.camera.core.CameraSession | import com.mrousavy.camera.core.CameraSession | ||||||
| import com.mrousavy.camera.core.InsufficientStorageError | import com.mrousavy.camera.core.InsufficientStorageError | ||||||
| import com.mrousavy.camera.utils.FileUtils |  | ||||||
| import com.mrousavy.camera.types.Flash | import com.mrousavy.camera.types.Flash | ||||||
|  | import com.mrousavy.camera.types.QualityPrioritization | ||||||
| import com.mrousavy.camera.utils.* | import com.mrousavy.camera.utils.* | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.io.FileOutputStream | import java.io.FileOutputStream | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
| import kotlinx.coroutines.* | import kotlinx.coroutines.* | ||||||
|  |  | ||||||
| private const val TAG = "CameraView.takeSnapshot" | private const val TAG = "CameraView.takePhoto" | ||||||
|  |  | ||||||
| @SuppressLint("UnsafeOptInUsageError") | @SuppressLint("UnsafeOptInUsageError") | ||||||
| suspend fun CameraView.takePhoto(optionsMap: ReadableMap): WritableMap { | suspend fun CameraView.takePhoto(optionsMap: ReadableMap): WritableMap { | ||||||
|   val options = optionsMap.toHashMap() |   val options = optionsMap.toHashMap() | ||||||
|   Log.i(TAG, "Taking snapshot... Options: $options") |   Log.i(TAG, "Taking photo... Options: $options") | ||||||
|   val bitmap = previewView.getBitmap() ?: throw Error() |  | ||||||
|  |  | ||||||
|   val file = FileUtils.createTempFile(context, "png"); |   val qualityPrioritization = options["qualityPrioritization"] as? String ?: "balanced" | ||||||
|  |   val flash = options["flash"] as? String ?: "off" | ||||||
|  |   val enableAutoStabilization = options["enableAutoStabilization"] == true | ||||||
|  |   val enableShutterSound = options["enableShutterSound"] as? Boolean ?: true | ||||||
|  |   val enablePrecapture = options["enablePrecapture"] as? Boolean ?: false | ||||||
|  |  | ||||||
|   // Write snapshot to .jpg file |   // TODO: Implement Red Eye Reduction | ||||||
|   FileUtils.writeBitmapTofile(bitmap, file, 100) |   options["enableAutoRedEyeReduction"] | ||||||
|  |  | ||||||
|   Log.i(TAG, "Successfully saved snapshot to file!") |   val flashMode = Flash.fromUnionValue(flash) | ||||||
|  |   val qualityPrioritizationMode = QualityPrioritization.fromUnionValue(qualityPrioritization) | ||||||
|  |  | ||||||
|  |   val photo = cameraSession.takePhoto( | ||||||
|  |     qualityPrioritizationMode, | ||||||
|  |     flashMode, | ||||||
|  |     enableShutterSound, | ||||||
|  |     enableAutoStabilization, | ||||||
|  |     enablePrecapture, | ||||||
|  |     orientation | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   photo.use { | ||||||
|  |     Log.i(TAG, "Successfully captured ${photo.image.width} x ${photo.image.height} photo!") | ||||||
|  |  | ||||||
|  |     val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId!!) | ||||||
|  |  | ||||||
|  |     val path = try { | ||||||
|  |       savePhotoToFile(context, cameraCharacteristics, photo) | ||||||
|  |     } catch (e: IOException) { | ||||||
|  |       if (e.message?.contains("no space left", true) == true) { | ||||||
|  |         throw InsufficientStorageError() | ||||||
|  |       } else { | ||||||
|  |         throw e | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Log.i(TAG, "Successfully saved photo to file! $path") | ||||||
|  |  | ||||||
|   // Parse output data |  | ||||||
|     val map = Arguments.createMap() |     val map = Arguments.createMap() | ||||||
|   map.putString("path", file.absolutePath) |     map.putString("path", path) | ||||||
|   map.putInt("width", bitmap.width) |     map.putInt("width", photo.image.width) | ||||||
|   map.putInt("height", bitmap.height) |     map.putInt("height", photo.image.height) | ||||||
|   map.putBoolean("isMirrored", false) |     map.putString("orientation", photo.orientation.unionValue) | ||||||
|  |     map.putBoolean("isRawPhoto", photo.format == ImageFormat.RAW_SENSOR) | ||||||
|  |     map.putBoolean("isMirrored", photo.isMirrored) | ||||||
|  |  | ||||||
|     return map |     return map | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| private fun writePhotoToFile(photo: CameraSession.CapturedPhoto, file: File) { | private fun writePhotoToFile(photo: CameraSession.CapturedPhoto, file: File) { | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ class CameraView(context: Context) : | |||||||
|  |  | ||||||
|   // session |   // session | ||||||
|   internal val cameraSession: CameraSession |   internal val cameraSession: CameraSession | ||||||
|   val previewView: PreviewView |   private val previewView: PreviewView | ||||||
|   private var currentConfigureCall: Long = System.currentTimeMillis() |   private var currentConfigureCall: Long = System.currentTimeMillis() | ||||||
|   internal var frameProcessor: FrameProcessor? = null |   internal var frameProcessor: FrameProcessor? = null | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,13 +2,9 @@ package com.mrousavy.camera.core | |||||||
|  |  | ||||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.content.res.Configuration |  | ||||||
| import android.graphics.Point | import android.graphics.Point | ||||||
| import android.os.Handler |  | ||||||
| import android.os.Looper |  | ||||||
| import android.util.Log | import android.util.Log | ||||||
| import android.util.Size | import android.util.Size | ||||||
| import android.view.PixelCopy |  | ||||||
| import android.view.SurfaceHolder | import android.view.SurfaceHolder | ||||||
| import android.view.SurfaceView | import android.view.SurfaceView | ||||||
| import com.facebook.react.bridge.UiThreadUtil | import com.facebook.react.bridge.UiThreadUtil | ||||||
| @@ -16,69 +12,9 @@ import com.mrousavy.camera.extensions.resize | |||||||
| import com.mrousavy.camera.extensions.rotatedBy | import com.mrousavy.camera.extensions.rotatedBy | ||||||
| import com.mrousavy.camera.types.Orientation | import com.mrousavy.camera.types.Orientation | ||||||
| import com.mrousavy.camera.types.ResizeMode | import com.mrousavy.camera.types.ResizeMode | ||||||
| import kotlin.coroutines.resume |  | ||||||
| import kotlin.coroutines.resumeWithException |  | ||||||
| import kotlin.math.roundToInt | import kotlin.math.roundToInt | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.suspendCancellableCoroutine |  | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
| import android.graphics.Bitmap |  | ||||||
| import android.graphics.Matrix |  | ||||||
|  |  | ||||||
| fun rotateBitmap90CounterClockwise(source: Bitmap): Bitmap { |  | ||||||
|     val width = source.width |  | ||||||
|     val height = source.height |  | ||||||
|  |  | ||||||
|     // Create a new Bitmap with swapped width and height |  | ||||||
|     val rotatedBitmap = Bitmap.createBitmap(height, width, source.config ?: Bitmap.Config.ARGB_8888) |  | ||||||
|  |  | ||||||
|     for (y in 0 until height) { |  | ||||||
|         for (x in 0 until width) { |  | ||||||
|             // Set the pixel in the new position |  | ||||||
|             rotatedBitmap.setPixel(y, width - 1 - x, source.getPixel(x, y)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return rotatedBitmap |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fun Bitmap.transformBitmap(orientation: Orientation): Bitmap { |  | ||||||
|     return when (orientation) { |  | ||||||
|         Orientation.PORTRAIT -> this // No transformation needed |  | ||||||
|         Orientation.LANDSCAPE_LEFT -> { |  | ||||||
|             // Transpose (swap width and height) |  | ||||||
|             val transposedBitmap = Bitmap.createBitmap(height, width, config ?: Bitmap.Config.ARGB_8888) |  | ||||||
|             for (y in 0 until height) { |  | ||||||
|                 for (x in 0 until width) { |  | ||||||
|                     transposedBitmap.setPixel(y, width - 1 - x, getPixel(x, y)) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             transposedBitmap |  | ||||||
|         } |  | ||||||
|         Orientation.PORTRAIT_UPSIDE_DOWN -> { |  | ||||||
|             // Invert vertically and horizontally (180-degree rotation) |  | ||||||
|             val invertedBitmap = Bitmap.createBitmap(width, height, config ?: Bitmap.Config.ARGB_8888) |  | ||||||
|             for (y in 0 until height) { |  | ||||||
|                 for (x in 0 until width) { |  | ||||||
|                     invertedBitmap.setPixel(width - 1 - x, height - 1 - y, getPixel(x, y)) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             invertedBitmap |  | ||||||
|         } |  | ||||||
|         Orientation.LANDSCAPE_RIGHT -> { |  | ||||||
|             // Transpose (swap width and height) and invert vertically |  | ||||||
|             val transposedBitmap = Bitmap.createBitmap(height, width, config ?: Bitmap.Config.ARGB_8888) |  | ||||||
|             for (y in 0 until height) { |  | ||||||
|                 for (x in 0 until width) { |  | ||||||
|                     transposedBitmap.setPixel(height - 1 - y, x, getPixel(x, y)) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             transposedBitmap |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @SuppressLint("ViewConstructor") | @SuppressLint("ViewConstructor") | ||||||
| class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | ||||||
| @@ -144,34 +80,6 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   suspend fun getBitmap(): Bitmap? = withContext(Dispatchers.Main) { |  | ||||||
|     val frame = holder.getSurfaceFrame() |  | ||||||
|  |  | ||||||
|     val width = frame.width() |  | ||||||
|     val height = frame.height() |  | ||||||
|  |  | ||||||
|     val bitmap = Bitmap.createBitmap(height, width, Bitmap.Config.ARGB_8888) |  | ||||||
|  |  | ||||||
|     // Use a coroutine to suspend until the PixelCopy request is complete |  | ||||||
|     suspendCancellableCoroutine<Bitmap?> { continuation -> |  | ||||||
|     PixelCopy.request( |  | ||||||
|         holder.surface, |  | ||||||
|         bitmap, |  | ||||||
|         { copyResult -> |  | ||||||
|           if (copyResult == PixelCopy.SUCCESS) { |  | ||||||
|             continuation.resume(rotateBitmap90CounterClockwise(bitmap)) |  | ||||||
|           } else { |  | ||||||
|             continuation.resumeWithException( |  | ||||||
|                 RuntimeException("PixelCopy failed with error code $copyResult") |  | ||||||
|             ) |  | ||||||
|           } |  | ||||||
|         }, |  | ||||||
|         Handler(Looper.getMainLooper()) |  | ||||||
|     ) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   fun convertLayerPointToCameraCoordinates(point: Point, cameraDeviceDetails: CameraDeviceDetails): Point { |   fun convertLayerPointToCameraCoordinates(point: Point, cameraDeviceDetails: CameraDeviceDetails): Point { | ||||||
|     val sensorOrientation = cameraDeviceDetails.sensorOrientation |     val sensorOrientation = cameraDeviceDetails.sensorOrientation | ||||||
|     val cameraSize = Size(cameraDeviceDetails.activeSize.width(), cameraDeviceDetails.activeSize.height()) |     val cameraSize = Size(cameraDeviceDetails.activeSize.width(), cameraDeviceDetails.activeSize.height()) | ||||||
| @@ -189,14 +97,22 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size { |   override fun requestLayout() { | ||||||
|     var contentSize = contentSize |     super.requestLayout() | ||||||
|     var androidOrientation = context.getResources().getConfiguration().orientation; |     // Manually trigger measure & layout, as RN on Android skips those. | ||||||
|  |     // See this issue: https://github.com/facebook/react-native/issues/17968#issuecomment-721958427 | ||||||
|     if (androidOrientation == Configuration.ORIENTATION_LANDSCAPE) { |     post { | ||||||
|       contentSize = Size(contentSize.height, contentSize.width) |       measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)) | ||||||
|  |       layout(left, top, right, bottom) | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size { | ||||||
|  |     var contentSize = contentSize | ||||||
|  |     // Swap dimensions if orientation is landscape | ||||||
|  |     if (orientation.isLandscape()) { | ||||||
|  |       contentSize = Size(contentSize.height, contentSize.width) | ||||||
|  |     } | ||||||
|     val contentAspectRatio = contentSize.width.toDouble() / contentSize.height |     val contentAspectRatio = contentSize.width.toDouble() / contentSize.height | ||||||
|     val containerAspectRatio = containerSize.width.toDouble() / containerSize.height |     val containerAspectRatio = containerSize.width.toDouble() / containerSize.height | ||||||
|     if (!(contentAspectRatio > 0 && containerAspectRatio > 0)) { |     if (!(contentAspectRatio > 0 && containerAspectRatio > 0)) { | ||||||
|   | |||||||
| @@ -1,30 +1,10 @@ | |||||||
| package com.mrousavy.camera.utils | package com.mrousavy.camera.utils | ||||||
|  |  | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.graphics.Bitmap |  | ||||||
| import android.graphics.BitmapFactory |  | ||||||
| import android.util.Size |  | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.io.FileOutputStream |  | ||||||
|  |  | ||||||
| class FileUtils { | class FileUtils { | ||||||
|   companion object { |   companion object { | ||||||
|     fun writeBitmapTofile(bitmap: Bitmap, file: File, quality: Int) { |  | ||||||
|       FileOutputStream(file).use { stream -> |  | ||||||
|         bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun getImageSize(imagePath: String): Size { |  | ||||||
|       val bitmapOptions = BitmapFactory.Options().also { |  | ||||||
|         it.inJustDecodeBounds = true |  | ||||||
|       } |  | ||||||
|       BitmapFactory.decodeFile(imagePath, bitmapOptions) |  | ||||||
|       val width = bitmapOptions.outWidth |  | ||||||
|       val height = bitmapOptions.outHeight |  | ||||||
|       return Size(width, height) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun createTempFile(context: Context, extension: String): File = |     fun createTempFile(context: Context, extension: String): File = | ||||||
|       File.createTempFile("mrousavy", extension, context.cacheDir).also { |       File.createTempFile("mrousavy", extension, context.cacheDir).also { | ||||||
|         it.deleteOnExit() |         it.deleteOnExit() | ||||||
|   | |||||||
| @@ -50,12 +50,4 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud | |||||||
|   func resumeRecording(promise: Promise) { |   func resumeRecording(promise: Promise) { | ||||||
|     cameraSession.resumeRecording(promise: promise) |     cameraSession.resumeRecording(promise: promise) | ||||||
|   } |   } | ||||||
|    |  | ||||||
|   func lockExposure(promise: Promise) { |  | ||||||
|     cameraSession.lockCurrentExposure(promise: promise) |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   func unlockExposure(promise: Promise) { |  | ||||||
|     cameraSession.unlockCurrentExposure(promise: promise) |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -86,13 +86,5 @@ RCT_EXTERN_METHOD(focus | |||||||
|                   : (NSDictionary*)point resolve |                   : (NSDictionary*)point resolve | ||||||
|                   : (RCTPromiseResolveBlock)resolve reject |                   : (RCTPromiseResolveBlock)resolve reject | ||||||
|                   : (RCTPromiseRejectBlock)reject); |                   : (RCTPromiseRejectBlock)reject); | ||||||
| RCT_EXTERN_METHOD(lockCurrentExposure |  | ||||||
|                   : (nonnull NSNumber*)node resolve |  | ||||||
|                   : (RCTPromiseResolveBlock)resolve reject |  | ||||||
|                   : (RCTPromiseRejectBlock)reject); |  | ||||||
| RCT_EXTERN_METHOD(unlockCurrentExposure |  | ||||||
|                   : (nonnull NSNumber*)node resolve |  | ||||||
|                   : (RCTPromiseResolveBlock)resolve reject |  | ||||||
|                   : (RCTPromiseRejectBlock)reject); |  | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -111,18 +111,6 @@ final class CameraViewManager: RCTViewManager { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @objc |  | ||||||
|   final func lockCurrentExposure(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { |  | ||||||
|     let component = getCameraView(withTag: node) |  | ||||||
|     component.lockExposure(promise: Promise(resolver: resolve, rejecter: reject)) |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   @objc |  | ||||||
|   final func unlockCurrentExposure(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { |  | ||||||
|     let component = getCameraView(withTag: node) |  | ||||||
|     component.unlockExposure(promise: Promise(resolver: resolve, rejecter: reject)) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // MARK: Private |   // MARK: Private | ||||||
|  |  | ||||||
|   private func getCameraView(withTag tag: NSNumber) -> CameraView { |   private func getCameraView(withTag tag: NSNumber) -> CameraView { | ||||||
|   | |||||||
| @@ -191,68 +191,4 @@ extension CameraSession { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   func lockCurrentExposure(promise: Promise) { |  | ||||||
|     CameraQueues.cameraQueue.async { |  | ||||||
|       withPromise(promise) { |  | ||||||
|         guard let captureDevice = AVCaptureDevice.default(for: .video) else { |  | ||||||
|           print("No capture device available") |  | ||||||
|           return |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         guard captureDevice.isExposureModeSupported(.custom) else { |  | ||||||
|           ReactLogger.log(level: .info, message: "Custom exposure mode not supported") |  | ||||||
|           return |  | ||||||
|         } |  | ||||||
|         do { |  | ||||||
|           // Lock the device for configuration |  | ||||||
|           try captureDevice.lockForConfiguration() |  | ||||||
|  |  | ||||||
|           // Get the current exposure duration and ISO |  | ||||||
|           let currentExposureDuration = captureDevice.exposureDuration |  | ||||||
|           let currentISO = captureDevice.iso |  | ||||||
|  |  | ||||||
|           // Check if the device supports custom exposure settings |  | ||||||
|           if captureDevice.isExposureModeSupported(.custom) { |  | ||||||
|             // Lock the current exposure and ISO by setting custom exposure mode |  | ||||||
|             captureDevice.setExposureModeCustom(duration: currentExposureDuration, iso: currentISO, completionHandler: nil) |  | ||||||
|             ReactLogger.log(level: .info, message: "Exposure and ISO locked at current values") |  | ||||||
|           } else { |  | ||||||
|             ReactLogger.log(level: .info, message:"Custom exposure mode not supported") |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           // Unlock the device after configuration |  | ||||||
|           captureDevice.unlockForConfiguration() |  | ||||||
|  |  | ||||||
|         } catch { |  | ||||||
|           ReactLogger.log(level: .warning, message:"Error locking exposure: \(error)") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return nil |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   func unlockCurrentExposure(promise: Promise) { |  | ||||||
|     CameraQueues.cameraQueue.async { |  | ||||||
|       withPromise(promise) { |  | ||||||
|         guard let captureDevice = AVCaptureDevice.default(for: .video) else { |  | ||||||
|           print("No capture device available") |  | ||||||
|           return |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         do { |  | ||||||
|           if captureDevice.isExposureModeSupported(.autoExpose) { |  | ||||||
|             try captureDevice.lockForConfiguration() |  | ||||||
|             captureDevice.exposureMode = .continuousAutoExposure |  | ||||||
|             captureDevice.unlockForConfiguration() |  | ||||||
|           } |  | ||||||
|         } catch { |  | ||||||
|           ReactLogger.log(level: .warning, message:"Error unlocking exposure: \(error)") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return nil |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -319,22 +319,6 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> { | |||||||
|       throw tryParseNativeCameraError(e) |       throw tryParseNativeCameraError(e) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async lockCurrentExposure(): Promise<void> { |  | ||||||
|     try { |  | ||||||
|       return await CameraModule.lockCurrentExposure(this.handle) |  | ||||||
|     } catch (e) { |  | ||||||
|       throw tryParseNativeCameraError(e) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public async unlockCurrentExposure(): Promise<void> { |  | ||||||
|     try { |  | ||||||
|       return await CameraModule.unlockCurrentExposure(this.handle) |  | ||||||
|     } catch (e) { |  | ||||||
|       throw tryParseNativeCameraError(e) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   //#endregion |   //#endregion | ||||||
|  |  | ||||||
|   //#region Static Functions (NativeModule) |   //#region Static Functions (NativeModule) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user