Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			eyenov/pre
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					c994506abc | 
@@ -13,37 +13,70 @@ import com.facebook.react.bridge.ReadableMap
 | 
			
		||||
import com.facebook.react.bridge.WritableMap
 | 
			
		||||
import com.mrousavy.camera.core.CameraSession
 | 
			
		||||
import com.mrousavy.camera.core.InsufficientStorageError
 | 
			
		||||
import com.mrousavy.camera.utils.FileUtils
 | 
			
		||||
import com.mrousavy.camera.types.Flash
 | 
			
		||||
import com.mrousavy.camera.types.QualityPrioritization
 | 
			
		||||
import com.mrousavy.camera.utils.*
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.FileOutputStream
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
 | 
			
		||||
private const val TAG = "CameraView.takeSnapshot"
 | 
			
		||||
private const val TAG = "CameraView.takePhoto"
 | 
			
		||||
 | 
			
		||||
@SuppressLint("UnsafeOptInUsageError")
 | 
			
		||||
suspend fun CameraView.takePhoto(optionsMap: ReadableMap): WritableMap {
 | 
			
		||||
  val options = optionsMap.toHashMap()
 | 
			
		||||
  Log.i(TAG, "Taking snapshot... Options: $options")
 | 
			
		||||
  val bitmap = previewView.getBitmap() ?: throw Error()
 | 
			
		||||
  Log.i(TAG, "Taking photo... Options: $options")
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
  FileUtils.writeBitmapTofile(bitmap, file, 100)
 | 
			
		||||
  // TODO: Implement Red Eye Reduction
 | 
			
		||||
  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()
 | 
			
		||||
  map.putString("path", file.absolutePath)
 | 
			
		||||
  map.putInt("width", bitmap.width)
 | 
			
		||||
  map.putInt("height", bitmap.height)
 | 
			
		||||
  map.putBoolean("isMirrored", false)
 | 
			
		||||
    map.putString("path", path)
 | 
			
		||||
    map.putInt("width", photo.image.width)
 | 
			
		||||
    map.putInt("height", photo.image.height)
 | 
			
		||||
    map.putString("orientation", photo.orientation.unionValue)
 | 
			
		||||
    map.putBoolean("isRawPhoto", photo.format == ImageFormat.RAW_SENSOR)
 | 
			
		||||
    map.putBoolean("isMirrored", photo.isMirrored)
 | 
			
		||||
 | 
			
		||||
    return map
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun writePhotoToFile(photo: CameraSession.CapturedPhoto, file: File) {
 | 
			
		||||
  val byteBuffer = photo.image.planes[0].buffer
 | 
			
		||||
 
 | 
			
		||||
@@ -102,7 +102,7 @@ class CameraView(context: Context) :
 | 
			
		||||
 | 
			
		||||
  // session
 | 
			
		||||
  internal val cameraSession: CameraSession
 | 
			
		||||
  val previewView: PreviewView
 | 
			
		||||
  private val previewView: PreviewView
 | 
			
		||||
  private var currentConfigureCall: Long = System.currentTimeMillis()
 | 
			
		||||
  internal var frameProcessor: FrameProcessor? = null
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,9 @@ package com.mrousavy.camera.core
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.res.Configuration
 | 
			
		||||
import android.graphics.Point
 | 
			
		||||
import android.os.Handler
 | 
			
		||||
import android.os.Looper
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.util.Size
 | 
			
		||||
import android.view.PixelCopy
 | 
			
		||||
import android.view.SurfaceHolder
 | 
			
		||||
import android.view.SurfaceView
 | 
			
		||||
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.types.Orientation
 | 
			
		||||
import com.mrousavy.camera.types.ResizeMode
 | 
			
		||||
import kotlin.coroutines.resume
 | 
			
		||||
import kotlin.coroutines.resumeWithException
 | 
			
		||||
import kotlin.math.roundToInt
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.suspendCancellableCoroutine
 | 
			
		||||
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")
 | 
			
		||||
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 {
 | 
			
		||||
    val sensorOrientation = cameraDeviceDetails.sensorOrientation
 | 
			
		||||
    val cameraSize = Size(cameraDeviceDetails.activeSize.width(), cameraDeviceDetails.activeSize.height())
 | 
			
		||||
@@ -189,47 +97,14 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) :
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size {
 | 
			
		||||
    var contentSize = contentSize
 | 
			
		||||
    var androidOrientation = context.getResources().getConfiguration().orientation;
 | 
			
		||||
 | 
			
		||||
    if (androidOrientation == Configuration.ORIENTATION_LANDSCAPE) {
 | 
			
		||||
      contentSize = Size(contentSize.height, contentSize.width)
 | 
			
		||||
  override fun requestLayout() {
 | 
			
		||||
    super.requestLayout()
 | 
			
		||||
    // Manually trigger measure & layout, as RN on Android skips those.
 | 
			
		||||
    // See this issue: https://github.com/facebook/react-native/issues/17968#issuecomment-721958427
 | 
			
		||||
    post {
 | 
			
		||||
      measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
 | 
			
		||||
      layout(left, top, right, bottom)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val contentAspectRatio = contentSize.width.toDouble() / contentSize.height
 | 
			
		||||
    val containerAspectRatio = containerSize.width.toDouble() / containerSize.height
 | 
			
		||||
    if (!(contentAspectRatio > 0 && containerAspectRatio > 0)) {
 | 
			
		||||
      // One of the aspect ratios is 0 or NaN, maybe the view hasn't been laid out yet.
 | 
			
		||||
      return contentSize
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val widthOverHeight = when (resizeMode) {
 | 
			
		||||
      ResizeMode.COVER -> contentAspectRatio > containerAspectRatio
 | 
			
		||||
      ResizeMode.CONTAIN -> contentAspectRatio < containerAspectRatio
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return if (widthOverHeight) {
 | 
			
		||||
      // Scale by width to cover height
 | 
			
		||||
      val scaledWidth = containerSize.height * contentAspectRatio
 | 
			
		||||
      Size(scaledWidth.roundToInt(), containerSize.height)
 | 
			
		||||
    } else {
 | 
			
		||||
      // Scale by height to cover width
 | 
			
		||||
      val scaledHeight = containerSize.width / contentAspectRatio
 | 
			
		||||
      Size(containerSize.width, scaledHeight.roundToInt())
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressLint("DrawAllocation")
 | 
			
		||||
  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
 | 
			
		||||
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
 | 
			
		||||
 | 
			
		||||
    val measuredViewSize = Size(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec))
 | 
			
		||||
    val surfaceSize = size.rotatedBy(inputOrientation)
 | 
			
		||||
    val fittedSize = getSize(surfaceSize, measuredViewSize, resizeMode)
 | 
			
		||||
 | 
			
		||||
    Log.i(TAG, "PreviewView is $measuredViewSize rendering $surfaceSize orientation ($orientation). Resizing to: $fittedSize ($resizeMode)")
 | 
			
		||||
    setMeasuredDimension(fittedSize.width, fittedSize.height)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,10 @@
 | 
			
		||||
package com.mrousavy.camera.utils
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.graphics.Bitmap
 | 
			
		||||
import android.graphics.BitmapFactory
 | 
			
		||||
import android.util.Size
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.FileOutputStream
 | 
			
		||||
 | 
			
		||||
class FileUtils {
 | 
			
		||||
  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 =
 | 
			
		||||
      File.createTempFile("mrousavy", extension, context.cacheDir).also {
 | 
			
		||||
        it.deleteOnExit()
 | 
			
		||||
 
 | 
			
		||||
@@ -50,12 +50,4 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud
 | 
			
		||||
  func 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
 | 
			
		||||
                  : (RCTPromiseResolveBlock)resolve 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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
  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)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
  //#region Static Functions (NativeModule)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user