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
This commit is contained in:
davidlcorbitt 2022-06-14 02:54:33 -06:00 committed by GitHub
parent fb2156ec39
commit 096b1cca4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 8 deletions

View File

@ -235,6 +235,8 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
override fun onHostResume() { override fun onHostResume() {
hostLifecycleState = Lifecycle.State.RESUMED hostLifecycleState = Lifecycle.State.RESUMED
updateLifecycleState() updateLifecycleState()
// workaround for https://issuetracker.google.com/issues/147354615, preview must be bound on resume
update(propsThatRequireSessionReconfiguration)
} }
override fun onHostPause() { override fun onHostPause() {
hostLifecycleState = Lifecycle.State.CREATED hostLifecycleState = Lifecycle.State.CREATED
@ -410,16 +412,21 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
// 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.i(TAG, "Using custom format - photo: ${format.photoSize}, video: ${format.videoSize} @ $fps FPS") Log.i(TAG, "Using custom format - photo: ${format.photoSize}, video: ${format.videoSize} @ $fps FPS")
if (video == true) {
previewBuilder.setTargetResolution(format.videoSize) previewBuilder.setTargetResolution(format.videoSize)
} else {
previewBuilder.setTargetResolution(format.photoSize)
}
imageCaptureBuilder.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... // TODO: Ability to select resolution exactly depending on format? Just like on iOS...
when (min(format.videoSize.height, format.videoSize.width)) { when (min(format.videoSize.height, format.videoSize.width)) {
in 0..480 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.SD)) in 0..480 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.SD))
in 480..720 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.HD)) in 480..720 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.HD, FallbackStrategy.lowerQualityThan(Quality.HD)))
in 720..1080 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.FHD)) in 720..1080 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.FHD, FallbackStrategy.lowerQualityThan(Quality.FHD)))
in 1080..2160 -> videoRecorderBuilder.setQualitySelector(QualitySelector.from(Quality.UHD)) 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 -> fps?.let { fps ->

View File

@ -7,10 +7,12 @@ import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager import android.hardware.camera2.CameraManager
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import android.util.Size
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
import androidx.camera.extensions.ExtensionMode import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager import androidx.camera.extensions.ExtensionsManager
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.QualitySelector
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.facebook.react.bridge.* import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule import com.facebook.react.module.annotations.ReactModule
@ -227,6 +229,16 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
} }
map.putDouble("neutralZoom", 1.0) map.putDouble("neutralZoom", 1.0)
val supportedVideoResolutions: List<Size>
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? // TODO: Optimize?
val maxImageOutputSize = cameraConfig.outputFormats val maxImageOutputSize = cameraConfig.outputFormats
.flatMap { cameraConfig.getOutputSizes(it).toList() } .flatMap { cameraConfig.getOutputSizes(it).toList() }
@ -234,7 +246,6 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
val formats = Arguments.createArray() val formats = Arguments.createArray()
// TODO: Get supported video qualities with QualitySelector.getSupportedQualities(...)
cameraConfig.outputFormats.forEach { formatId -> cameraConfig.outputFormats.forEach { formatId ->
val formatName = parseImageFormat(formatId) val formatName = parseImageFormat(formatId)
@ -287,8 +298,12 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
val format = Arguments.createMap() val format = Arguments.createMap()
format.putDouble("photoHeight", size.height.toDouble()) format.putDouble("photoHeight", size.height.toDouble())
format.putDouble("photoWidth", size.width.toDouble()) format.putDouble("photoWidth", size.width.toDouble())
format.putDouble("videoHeight", size.height.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoHeight == photoHeight?) // since supportedVideoResolutions is sorted from highest resolution to lowest,
format.putDouble("videoWidth", size.width.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoWidth == photoWidth?) // 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.putBoolean("isHighestPhotoQualitySupported", isHighestPhotoQualitySupported)
format.putInt("maxISO", isoRange?.upper) format.putInt("maxISO", isoRange?.upper)
format.putInt("minISO", isoRange?.lower) format.putInt("minISO", isoRange?.lower)

View File

@ -36,7 +36,13 @@ export type CaptureError =
| 'capture/no-recording-in-progress' | 'capture/no-recording-in-progress'
| 'capture/file-io-error' | 'capture/file-io-error'
| 'capture/create-temp-file-error' | 'capture/create-temp-file-error'
| 'capture/invalid-video-options'
| 'capture/create-recorder-error' | '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/invalid-photo-codec'
| 'capture/not-bound-error' | 'capture/not-bound-error'
| 'capture/capture-type-not-supported' | 'capture/capture-type-not-supported'