From 18b30cd073fc43a60e51d081a0cc5df4d5d03a66 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 10 Oct 2023 18:51:46 +0200 Subject: [PATCH] fix: Always call `CaptureSession` fully synchronously under Mutex (#1972) * fix: Always call `CaptureSession` fully synchronously under Mutex * Update CameraView.kt * chore: Format --- .../java/com/mrousavy/camera/CameraView.kt | 75 ++++++++++--------- .../com/mrousavy/camera/core/CameraSession.kt | 42 ++++------- 2 files changed, 54 insertions(+), 63 deletions(-) diff --git a/package/android/src/main/java/com/mrousavy/camera/CameraView.kt b/package/android/src/main/java/com/mrousavy/camera/CameraView.kt index c4699c3..6cafe69 100644 --- a/package/android/src/main/java/com/mrousavy/camera/CameraView.kt +++ b/package/android/src/main/java/com/mrousavy/camera/CameraView.kt @@ -28,8 +28,8 @@ import com.mrousavy.camera.parsers.PixelFormat import com.mrousavy.camera.parsers.ResizeMode import com.mrousavy.camera.parsers.Torch import com.mrousavy.camera.parsers.VideoStabilizationMode +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch // @@ -42,7 +42,9 @@ import kotlinx.coroutines.launch // TODO: takePhoto() return with jsi::Value Image reference for faster capture @SuppressLint("ClickableViewAccessibility", "ViewConstructor", "MissingPermission") -class CameraView(context: Context) : FrameLayout(context) { +class CameraView(context: Context) : + FrameLayout(context), + CoroutineScope { companion object { const val TAG = "CameraView" @@ -104,6 +106,8 @@ class CameraView(context: Context) : FrameLayout(context) { internal val outputOrientation: Orientation get() = orientation ?: inputOrientation + override val coroutineContext: CoroutineContext = CameraQueues.cameraQueue.coroutineDispatcher + init { this.installHierarchyFitter() setupPreviewView() @@ -116,12 +120,12 @@ class CameraView(context: Context) : FrameLayout(context) { isMounted = true invokeOnViewReady() } - updateLifecycle() + launch { updateLifecycle() } } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - updateLifecycle() + launch { updateLifecycle() } } private fun getPreviewTargetSize(): Size { @@ -142,7 +146,7 @@ class CameraView(context: Context) : FrameLayout(context) { val previewView = PreviewView(context, this.getPreviewTargetSize(), resizeMode) { surface -> previewSurface = surface - configureSession() + launch { configureSession() } } previewView.layoutParams = LayoutParams( LayoutParams.MATCH_PARENT, @@ -164,27 +168,30 @@ class CameraView(context: Context) : FrameLayout(context) { val shouldCheckActive = shouldReconfigureFormat || changedProps.contains("isActive") val shouldReconfigureZoomGesture = changedProps.contains("enableZoomGesture") - if (shouldReconfigurePreview) { - setupPreviewView() - } - if (shouldReconfigureSession) { - configureSession() - } - if (shouldReconfigureFormat) { - configureFormat() - } - if (shouldCheckActive) { - updateLifecycle() - } - - if (shouldReconfigureZoom) { - updateZoom() - } - if (shouldReconfigureTorch) { - updateTorch() - } - if (shouldReconfigureZoomGesture) { - updateZoomGesture() + launch { + // Expensive Calls + if (shouldReconfigurePreview) { + setupPreviewView() + } + if (shouldReconfigureSession) { + configureSession() + } + if (shouldReconfigureFormat) { + configureFormat() + } + if (shouldCheckActive) { + updateLifecycle() + } + // Fast Calls + if (shouldReconfigureZoom) { + updateZoom() + } + if (shouldReconfigureTorch) { + updateTorch() + } + if (shouldReconfigureZoomGesture) { + updateZoomGesture() + } } } catch (e: Throwable) { Log.e(TAG, "update() threw: ${e.message}") @@ -192,7 +199,7 @@ class CameraView(context: Context) : FrameLayout(context) { } } - private fun configureSession() { + private suspend fun configureSession() { try { Log.i(TAG, "Configuring Camera Device...") @@ -236,22 +243,20 @@ class CameraView(context: Context) : FrameLayout(context) { } } - private fun configureFormat() { + private suspend fun configureFormat() { cameraSession.configureFormat(fps, videoStabilizationMode, hdr, lowLightBoost) } - private fun updateLifecycle() { + private suspend fun updateLifecycle() { cameraSession.setIsActive(isActive && isAttachedToWindow) } - private fun updateZoom() { + private suspend fun updateZoom() { cameraSession.setZoom(zoom) } - private fun updateTorch() { - CoroutineScope(Dispatchers.Default).launch { - cameraSession.setTorchMode(torch == Torch.ON) - } + private suspend fun updateTorch() { + cameraSession.setTorchMode(torch == Torch.ON) } @SuppressLint("ClickableViewAccessibility") @@ -262,7 +267,7 @@ class CameraView(context: Context) : FrameLayout(context) { object : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { zoom *= detector.scaleFactor - cameraSession.setZoom(zoom) + launch { updateZoom() } return true } } diff --git a/package/android/src/main/java/com/mrousavy/camera/core/CameraSession.kt b/package/android/src/main/java/com/mrousavy/camera/core/CameraSession.kt index 3f86b35..88dbedd 100644 --- a/package/android/src/main/java/com/mrousavy/camera/core/CameraSession.kt +++ b/package/android/src/main/java/com/mrousavy/camera/core/CameraSession.kt @@ -40,9 +40,6 @@ import com.mrousavy.camera.parsers.VideoFileType import com.mrousavy.camera.parsers.VideoStabilizationMode import java.io.Closeable import java.util.concurrent.CancellationException -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -52,7 +49,6 @@ class CameraSession( private val onInitialized: () -> Unit, private val onError: (e: Throwable) -> Unit ) : CameraManager.AvailabilityCallback(), - CoroutineScope, Closeable, CameraOutputs.Callback { companion object { @@ -112,8 +108,6 @@ class CameraSession( updateVideoOutputs() } - override val coroutineContext: CoroutineContext = CameraQueues.cameraQueue.coroutineDispatcher - init { cameraManager.registerAvailabilityCallback(this, CameraQueues.cameraQueue.handler) } @@ -135,7 +129,7 @@ class CameraSession( return Orientation.fromRotationDegrees(sensorRotation) } - fun configureSession( + suspend fun configureSession( cameraId: String, preview: CameraOutputs.PreviewOutput? = null, photo: CameraOutputs.PhotoOutput? = null, @@ -165,12 +159,10 @@ class CameraSession( updateVideoOutputs() this.cameraId = cameraId - launch { - startRunning() - } + startRunning() } - fun configureFormat( + suspend fun configureFormat( fps: Int? = null, videoStabilizationMode: VideoStabilizationMode? = null, hdr: Boolean? = null, @@ -198,29 +190,25 @@ class CameraSession( ) needsReconfiguration = true } - launch { - if (needsReconfiguration) { - startRunning() - } else { - updateRepeatingRequest() - } + if (needsReconfiguration) { + startRunning() + } else { + updateRepeatingRequest() } } /** * Starts or stops the Camera. */ - fun setIsActive(isActive: Boolean) { + suspend fun setIsActive(isActive: Boolean) { Log.i(TAG, "Setting isActive: $isActive (isRunning: $isRunning)") this.isActive = isActive if (isActive == isRunning) return - launch { - if (isActive) { - startRunning() - } else { - stopRunning() - } + if (isActive) { + startRunning() + } else { + stopRunning() } } @@ -328,12 +316,10 @@ class CameraSession( } } - fun setZoom(zoom: Float) { + suspend fun setZoom(zoom: Float) { if (this.zoom != zoom) { this.zoom = zoom - launch { - updateRepeatingRequest() - } + updateRepeatingRequest() } }