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:
Marc Rousavy 2021-03-12 10:45:23 +01:00 committed by GitHub
parent c4d7d81c36
commit d85126d883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 171 additions and 171 deletions

View File

@ -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) {

View File

@ -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) {

View File

@ -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)
}
}