From 096b1cca4ea66c80acdb9b87ea2136f95c36d7fe Mon Sep 17 00:00:00 2001 From: davidlcorbitt <41524992+davidlcorbitt@users.noreply.github.com> Date: Tue, 14 Jun 2022 02:54:33 -0600 Subject: [PATCH] fix: (Android) Give real video resolutions, unbind/rebind preview in onHostResume, add missing Android capture errors (#1079) * Calculate a format's video dimensions based on supported resolutions and photo dimensions * Add Android fallback strategy for recording quality * Ensure that session props are not ignored when app is resumed * Simplify setting Android video dimensions in format * Modify Android imageAnalysisBuilder to use photoSize * Update onHostResume function to reference android preview issue * Add missing Android capture errors --- .../java/com/mrousavy/camera/CameraView.kt | 17 ++++++++++----- .../com/mrousavy/camera/CameraViewModule.kt | 21 ++++++++++++++++--- src/CameraError.ts | 6 ++++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/mrousavy/camera/CameraView.kt b/android/src/main/java/com/mrousavy/camera/CameraView.kt index 507e9fd..e666cb5 100644 --- a/android/src/main/java/com/mrousavy/camera/CameraView.kt +++ b/android/src/main/java/com/mrousavy/camera/CameraView.kt @@ -235,6 +235,8 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer override fun onHostResume() { hostLifecycleState = Lifecycle.State.RESUMED updateLifecycleState() + // workaround for https://issuetracker.google.com/issues/147354615, preview must be bound on resume + update(propsThatRequireSessionReconfiguration) } override fun onHostPause() { hostLifecycleState = Lifecycle.State.CREATED @@ -410,16 +412,21 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer // User has selected a custom format={}. Use that val format = DeviceFormat(format!!) Log.i(TAG, "Using custom format - photo: ${format.photoSize}, video: ${format.videoSize} @ $fps FPS") - previewBuilder.setTargetResolution(format.videoSize) + if (video == true) { + previewBuilder.setTargetResolution(format.videoSize) + } else { + previewBuilder.setTargetResolution(format.photoSize) + } imageCaptureBuilder.setTargetResolution(format.photoSize) - imageAnalysisBuilder.setTargetResolution(format.videoSize) + imageAnalysisBuilder.setTargetResolution(format.photoSize) // TODO: Ability to select resolution exactly depending on format? Just like on iOS... when (min(format.videoSize.height, format.videoSize.width)) { in 0..480 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.SD)) - in 480..720 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.HD)) - in 720..1080 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.FHD)) - in 1080..2160 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.UHD)) + in 480..720 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.HD, FallbackStrategy.lowerQualityThan(Quality.HD))) + in 720..1080 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.FHD, FallbackStrategy.lowerQualityThan(Quality.FHD))) + in 1080..2160 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.UHD, FallbackStrategy.lowerQualityThan(Quality.UHD))) + in 2160..4320 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.HIGHEST, FallbackStrategy.lowerQualityThan(Quality.HIGHEST))) } fps?.let { fps -> diff --git a/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt b/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt index 5f1477b..8f88c1c 100644 --- a/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt +++ b/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt @@ -7,10 +7,12 @@ import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager import android.os.Build import android.util.Log +import android.util.Size import androidx.camera.core.CameraSelector import androidx.camera.extensions.ExtensionMode import androidx.camera.extensions.ExtensionsManager import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.video.QualitySelector import androidx.core.content.ContextCompat import com.facebook.react.bridge.* import com.facebook.react.module.annotations.ReactModule @@ -227,6 +229,16 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase } map.putDouble("neutralZoom", 1.0) + val supportedVideoResolutions: List + val cameraInfos = cameraSelector.filter(cameraProvider.availableCameraInfos) + if (cameraInfos.size > 0) { + supportedVideoResolutions = QualitySelector + .getSupportedQualities(cameraInfos[0]) + .map { QualitySelector.getResolution(cameraInfos[0], it)!! } + } else { + supportedVideoResolutions = emptyList() + } + // TODO: Optimize? val maxImageOutputSize = cameraConfig.outputFormats .flatMap { cameraConfig.getOutputSizes(it).toList() } @@ -234,7 +246,6 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase val formats = Arguments.createArray() - // TODO: Get supported video qualities with QualitySelector.getSupportedQualities(...) cameraConfig.outputFormats.forEach { formatId -> val formatName = parseImageFormat(formatId) @@ -287,8 +298,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase val format = Arguments.createMap() format.putDouble("photoHeight", size.height.toDouble()) format.putDouble("photoWidth", size.width.toDouble()) - format.putDouble("videoHeight", size.height.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoHeight == photoHeight?) - format.putDouble("videoWidth", size.width.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoWidth == photoWidth?) + // since supportedVideoResolutions is sorted from highest resolution to lowest, + // videoResolution will be the highest supported video resolution lower than or equal to photo resolution + // TODO: Somehow integrate with CamcorderProfileProxy? + val videoResolution = supportedVideoResolutions.find { it.width <= size.width && it.height <= size.height } + format.putDouble("videoHeight", videoResolution?.height?.toDouble()) + format.putDouble("videoWidth", videoResolution?.width?.toDouble()) format.putBoolean("isHighestPhotoQualitySupported", isHighestPhotoQualitySupported) format.putInt("maxISO", isoRange?.upper) format.putInt("minISO", isoRange?.lower) diff --git a/src/CameraError.ts b/src/CameraError.ts index 7d90a84..e434a45 100644 --- a/src/CameraError.ts +++ b/src/CameraError.ts @@ -36,7 +36,13 @@ export type CaptureError = | 'capture/no-recording-in-progress' | 'capture/file-io-error' | 'capture/create-temp-file-error' + | 'capture/invalid-video-options' | 'capture/create-recorder-error' + | 'capture/recorder-error' + | 'capture/no-valid-data' + | 'capture/inactive-source' + | 'capture/insufficient-storage' + | 'capture/file-size-limit-reached' | 'capture/invalid-photo-codec' | 'capture/not-bound-error' | 'capture/capture-type-not-supported'