Init ExtensionsManager and ProcessCameraProvider before checking Extension availability (#48)
* Init ExtensionsManager and ProcessCameraProvider before checking Extension availability * Remove withSuspendablePromise * Async init ProcessCameraProvider * Remove that unnecessary Future caching again * Post `update` on previewView Fixes "previewView.display must not be null!" error
This commit is contained in:
parent
c4d7d81c36
commit
d85126d883
@ -100,6 +100,7 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
private val reactContext: ReactContext
|
private val reactContext: ReactContext
|
||||||
get() = context as ReactContext
|
get() = context as ReactContext
|
||||||
|
|
||||||
|
@Suppress("JoinDeclarationAndAssignment")
|
||||||
internal val previewView: PreviewView
|
internal val previewView: PreviewView
|
||||||
private val cameraExecutor = Executors.newSingleThreadExecutor()
|
private val cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
internal val takePhotoExecutor = Executors.newSingleThreadExecutor()
|
internal val takePhotoExecutor = Executors.newSingleThreadExecutor()
|
||||||
@ -192,7 +193,11 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
/**
|
/**
|
||||||
* Invalidate all React Props and reconfigure the device
|
* Invalidate all React Props and reconfigure the device
|
||||||
*/
|
*/
|
||||||
fun update(changedProps: ArrayList<String>) = GlobalScope.launch(Dispatchers.Main) {
|
fun update(changedProps: ArrayList<String>) = previewView.post {
|
||||||
|
// TODO: Does this introduce too much overhead?
|
||||||
|
// I need to .post on the previewView because it might've not been initialized yet
|
||||||
|
// I need to use GlobalScope.launch because of the suspend fun [configureSession]
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
try {
|
try {
|
||||||
val shouldReconfigureSession = changedProps.containsAny(propsThatRequireSessionReconfiguration)
|
val shouldReconfigureSession = changedProps.containsAny(propsThatRequireSessionReconfiguration)
|
||||||
val shouldReconfigureZoom = shouldReconfigureSession || changedProps.contains("zoom")
|
val shouldReconfigureZoom = shouldReconfigureSession || changedProps.contains("zoom")
|
||||||
@ -218,6 +223,7 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
invokeOnError(e)
|
invokeOnError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the camera capture session. This should only be called when the camera device changes.
|
* Configures the camera capture session. This should only be called when the camera device changes.
|
||||||
@ -225,7 +231,8 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
@SuppressLint("UnsafeExperimentalUsageError", "RestrictedApi")
|
@SuppressLint("UnsafeExperimentalUsageError", "RestrictedApi")
|
||||||
private suspend fun configureSession() {
|
private suspend fun configureSession() {
|
||||||
try {
|
try {
|
||||||
Log.d(REACT_CLASS, "Configuring session...")
|
val startTime = System.currentTimeMillis()
|
||||||
|
Log.i(REACT_CLASS, "Configuring session...")
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||||
throw MicrophonePermissionError()
|
throw MicrophonePermissionError()
|
||||||
}
|
}
|
||||||
@ -236,12 +243,12 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
throw NoCameraDeviceError()
|
throw NoCameraDeviceError()
|
||||||
}
|
}
|
||||||
if (format != null)
|
if (format != null)
|
||||||
Log.d(REACT_CLASS, "Configuring session with Camera ID $cameraId and custom format...")
|
Log.i(REACT_CLASS, "Configuring session with Camera ID $cameraId and custom format...")
|
||||||
else
|
else
|
||||||
Log.d(REACT_CLASS, "Configuring session with Camera ID $cameraId and default format options...")
|
Log.i(REACT_CLASS, "Configuring session with Camera ID $cameraId and default format options...")
|
||||||
|
|
||||||
// Used to bind the lifecycle of cameras to the lifecycle owner
|
// Used to bind the lifecycle of cameras to the lifecycle owner
|
||||||
val cameraProvider = ProcessCameraProvider.getInstance(context).await()
|
val cameraProvider = ProcessCameraProvider.getInstance(reactContext).await()
|
||||||
|
|
||||||
val cameraSelector = CameraSelector.Builder().byID(cameraId!!).build()
|
val cameraSelector = CameraSelector.Builder().byID(cameraId!!).build()
|
||||||
|
|
||||||
@ -257,7 +264,7 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
|
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
// let CameraX automatically find best resolution for the target aspect ratio
|
// let CameraX automatically find best resolution for the target aspect ratio
|
||||||
Log.d(REACT_CLASS, "No custom format has been set, CameraX will automatically determine best configuration...")
|
Log.i(REACT_CLASS, "No custom format has been set, CameraX will automatically determine best configuration...")
|
||||||
val aspectRatio = aspectRatio(previewView.width, previewView.height)
|
val aspectRatio = aspectRatio(previewView.width, previewView.height)
|
||||||
previewBuilder.setTargetAspectRatio(aspectRatio)
|
previewBuilder.setTargetAspectRatio(aspectRatio)
|
||||||
imageCaptureBuilder.setTargetAspectRatio(aspectRatio)
|
imageCaptureBuilder.setTargetAspectRatio(aspectRatio)
|
||||||
@ -265,7 +272,7 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
} else {
|
} else {
|
||||||
// User has selected a custom format={}. Use that
|
// User has selected a custom format={}. Use that
|
||||||
val format = DeviceFormat(format!!)
|
val format = DeviceFormat(format!!)
|
||||||
Log.d(REACT_CLASS, "Using custom format - photo: ${format.photoSize}, video: ${format.videoSize} @ $fps FPS")
|
Log.i(REACT_CLASS, "Using custom format - photo: ${format.photoSize}, video: ${format.videoSize} @ $fps FPS")
|
||||||
previewBuilder.setDefaultResolution(format.photoSize)
|
previewBuilder.setDefaultResolution(format.photoSize)
|
||||||
imageCaptureBuilder.setDefaultResolution(format.photoSize)
|
imageCaptureBuilder.setDefaultResolution(format.photoSize)
|
||||||
videoCaptureBuilder.setDefaultResolution(format.photoSize)
|
videoCaptureBuilder.setDefaultResolution(format.photoSize)
|
||||||
@ -275,7 +282,7 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
// Camera supports the given FPS (frame rate range)
|
// Camera supports the given FPS (frame rate range)
|
||||||
val frameDuration = (1.0 / fps.toDouble()).toLong() * 1_000_000_000
|
val frameDuration = (1.0 / fps.toDouble()).toLong() * 1_000_000_000
|
||||||
|
|
||||||
Log.d(REACT_CLASS, "Setting AE_TARGET_FPS_RANGE to $fps-$fps, and SENSOR_FRAME_DURATION to $frameDuration")
|
Log.i(REACT_CLASS, "Setting AE_TARGET_FPS_RANGE to $fps-$fps, and SENSOR_FRAME_DURATION to $frameDuration")
|
||||||
Camera2Interop.Extender(previewBuilder)
|
Camera2Interop.Extender(previewBuilder)
|
||||||
.setCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(fps, fps))
|
.setCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(fps, fps))
|
||||||
.setCaptureRequestOption(CaptureRequest.SENSOR_FRAME_DURATION, frameDuration)
|
.setCaptureRequestOption(CaptureRequest.SENSOR_FRAME_DURATION, frameDuration)
|
||||||
@ -333,7 +340,8 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
minZoom = camera!!.cameraInfo.zoomState.value?.minZoomRatio ?: 1f
|
minZoom = camera!!.cameraInfo.zoomState.value?.minZoomRatio ?: 1f
|
||||||
maxZoom = camera!!.cameraInfo.zoomState.value?.maxZoomRatio ?: 1f
|
maxZoom = camera!!.cameraInfo.zoomState.value?.maxZoomRatio ?: 1f
|
||||||
|
|
||||||
Log.d(REACT_CLASS, "Session configured! Camera: ${camera!!}")
|
val duration = System.currentTimeMillis() - startTime
|
||||||
|
Log.i(REACT_CLASS, "Session configured in $duration ms! Camera: ${camera!!}")
|
||||||
invokeOnInitialized()
|
invokeOnInitialized()
|
||||||
} catch (exc: Throwable) {
|
} catch (exc: Throwable) {
|
||||||
throw when (exc) {
|
throw when (exc) {
|
||||||
|
@ -11,17 +11,18 @@ import android.os.Build
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.ImageCapture
|
import androidx.camera.core.ImageCapture
|
||||||
|
import androidx.camera.extensions.ExtensionsManager
|
||||||
import androidx.camera.extensions.HdrImageCaptureExtender
|
import androidx.camera.extensions.HdrImageCaptureExtender
|
||||||
import androidx.camera.extensions.NightImageCaptureExtender
|
import androidx.camera.extensions.NightImageCaptureExtender
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.facebook.react.bridge.*
|
import com.facebook.react.bridge.*
|
||||||
import com.facebook.react.modules.core.PermissionAwareActivity
|
import com.facebook.react.modules.core.PermissionAwareActivity
|
||||||
import com.facebook.react.modules.core.PermissionListener
|
import com.facebook.react.modules.core.PermissionListener
|
||||||
import com.mrousavy.camera.parsers.*
|
import com.mrousavy.camera.parsers.*
|
||||||
import com.mrousavy.camera.utils.*
|
import com.mrousavy.camera.utils.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.guava.await
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
||||||
companion object {
|
companion object {
|
||||||
@ -109,9 +110,16 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This uses the Camera2 API to list all characteristics of a camera device and therefore doesn't work with Camera1. Find a way to use CameraX for this
|
// TODO: This uses the Camera2 API to list all characteristics of a camera device and therefore doesn't work with Camera1. Find a way to use CameraX for this
|
||||||
|
// https://issuetracker.google.com/issues/179925896
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
fun getAvailableCameraDevices(promise: Promise) {
|
fun getAvailableCameraDevices(promise: Promise) {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
withPromise(promise) {
|
withPromise(promise) {
|
||||||
|
// I need to init those because the HDR/Night Mode Extension expects them to be initialized
|
||||||
|
val extensionsManager = ExtensionsManager.init(reactApplicationContext).await()
|
||||||
|
val processCameraProvider = ProcessCameraProvider.getInstance(reactApplicationContext).await()
|
||||||
|
|
||||||
val manager = reactApplicationContext.getSystemService(Context.CAMERA_SERVICE) as? CameraManager
|
val manager = reactApplicationContext.getSystemService(Context.CAMERA_SERVICE) as? CameraManager
|
||||||
?: throw CameraManagerUnavailableError()
|
?: throw CameraManagerUnavailableError()
|
||||||
|
|
||||||
@ -157,18 +165,10 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|||||||
else null
|
else null
|
||||||
val fpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)!!
|
val fpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)!!
|
||||||
|
|
||||||
var supportsHdr = false
|
|
||||||
var supportsLowLightBoost = false
|
|
||||||
try {
|
|
||||||
val hdrExtension = HdrImageCaptureExtender.create(imageCaptureBuilder)
|
val hdrExtension = HdrImageCaptureExtender.create(imageCaptureBuilder)
|
||||||
supportsHdr = hdrExtension.isExtensionAvailable(cameraSelector)
|
val supportsHdr = hdrExtension.isExtensionAvailable(cameraSelector)
|
||||||
|
|
||||||
val nightExtension = NightImageCaptureExtender.create(imageCaptureBuilder)
|
val nightExtension = NightImageCaptureExtender.create(imageCaptureBuilder)
|
||||||
supportsLowLightBoost = nightExtension.isExtensionAvailable(cameraSelector)
|
val supportsLowLightBoost = nightExtension.isExtensionAvailable(cameraSelector)
|
||||||
} catch (e: Throwable) {
|
|
||||||
// error on checking availability. falls back to "false"
|
|
||||||
Log.e(REACT_CLASS, "Failed to check HDR/Night Mode extension availability.", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
val fieldOfView = characteristics.getFieldOfView()
|
val fieldOfView = characteristics.getFieldOfView()
|
||||||
|
|
||||||
@ -254,9 +254,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|||||||
cameraDevices.pushMap(map)
|
cameraDevices.pushMap(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val difference = System.currentTimeMillis() - startTime
|
||||||
|
Log.w(REACT_CLASS, "CameraViewModule::getAvailableCameraDevices took: $difference ms")
|
||||||
return@withPromise cameraDevices
|
return@withPromise cameraDevices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
fun getCameraPermissionStatus(promise: Promise) {
|
fun getCameraPermissionStatus(promise: Promise) {
|
||||||
|
@ -14,14 +14,3 @@ inline fun withPromise(promise: Promise, closure: () -> Any?) {
|
|||||||
promise.reject("${error.domain}/${error.id}", error.message, error.cause)
|
promise.reject("${error.domain}/${error.id}", error.message, error.cause)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun withSuspendablePromise(promise: Promise, closure: () -> Any?) {
|
|
||||||
try {
|
|
||||||
val result = closure()
|
|
||||||
promise.resolve(result)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
e.printStackTrace()
|
|
||||||
val error = if (e is CameraError) e else UnknownCameraError(e)
|
|
||||||
promise.reject("${error.domain}/${error.id}", error.message, error.cause)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user