fix: Fix blackscreen issues and lifecycle when closing Camera (#2339)

* fix: Fix Blackscreen by deterministically destroying session if `isActive=false`

* Re-open Camera if session died

* Simplify Camera

* Disconnect is optional, block when resetting state

* fix: Log in `configure { ... }`

* fix: Make concurrent configure safe

* fix: Don't resize preview

* fix: Use current `CameraConfiguration`

* Don't start if no outputs are available

* Only mount with preview outputs

* Update CameraSession.kt

* Update PreviewView.kt

* Better logging

* Update CameraSession.kt

* Extract

* fix: Rebuild entire session if `isActive` changed

* isActive safe

* Start session at 1

* Create ActiveCameraDevice.kt

* interrupts

* chore: Freeze `frame` in `useFrameProcessor`

* Revert "chore: Freeze `frame` in `useFrameProcessor`"

This reverts commit dff93d506e29a791d8dea8842b880ab5c892211e.

* chore: Better logging

* fix: Move HDR to `video`/`photo` config

* fix: Fix hdr usage

* fix: Ignore any updates after destroying Camera

* fix: Fix video HDR

* chore: Format code

* fix: Check Camera permission

* Remove unneeded error

* Update CameraSession.kt

* Update CameraPage.tsx

* Delete OutputConfiguration.toDebugString.kt

* Update CameraSession.kt
This commit is contained in:
Marc Rousavy
2024-01-08 11:41:57 +01:00
committed by GitHub
parent 2cd22ad236
commit 0d21bc3a57
16 changed files with 297 additions and 239 deletions

View File

@@ -4,22 +4,22 @@ import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration
import android.os.Build
import android.util.Log
import com.mrousavy.camera.core.CameraQueues
import com.mrousavy.camera.core.CameraSessionCannotBeConfiguredError
import com.mrousavy.camera.core.outputs.SurfaceOutput
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
private const val TAG = "CreateCaptureSession"
private var sessionId = 1000
private var sessionId = 1
suspend fun CameraDevice.createCaptureSession(
cameraManager: CameraManager,
outputs: List<OutputConfiguration>,
outputs: List<SurfaceOutput>,
onClosed: (session: CameraCaptureSession) -> Unit,
queue: CameraQueues.CameraQueue
): CameraCaptureSession =
@@ -29,34 +29,35 @@ suspend fun CameraDevice.createCaptureSession(
val sessionId = sessionId++
Log.i(
TAG,
"Camera $id: Creating Capture Session #$sessionId... " +
"Hardware Level: $hardwareLevel} | Outputs: $outputs"
"Camera #$id: Creating Capture Session #$sessionId... " +
"(Hardware Level: $hardwareLevel | Outputs: [${outputs.joinToString()}])"
)
val callback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
Log.i(TAG, "Camera $id: Capture Session #$sessionId configured!")
Log.i(TAG, "Camera #$id: Successfully created CameraCaptureSession #$sessionId!")
continuation.resume(session)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
Log.e(TAG, "Camera $id: Failed to configure Capture Session #$sessionId!")
Log.e(TAG, "Camera #$id: Failed to create CameraCaptureSession #$sessionId!")
continuation.resumeWithException(CameraSessionCannotBeConfiguredError(id))
}
override fun onClosed(session: CameraCaptureSession) {
Log.i(TAG, "Camera #$id: CameraCaptureSession #$sessionId has been closed.")
super.onClosed(session)
Log.i(TAG, "Camera $id: Capture Session #$sessionId closed!")
onClosed(session)
}
}
val configurations = outputs.map { it.toOutputConfiguration(characteristics) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Log.i(TAG, "Using new API (>=28)")
val config = SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outputs, queue.executor, callback)
val config = SessionConfiguration(SessionConfiguration.SESSION_REGULAR, configurations, queue.executor, callback)
this.createCaptureSession(config)
} else {
Log.i(TAG, "Using legacy API (<28)")
this.createCaptureSessionByOutputConfigurations(outputs, callback, queue.handler)
this.createCaptureSessionByOutputConfigurations(configurations, callback, queue.handler)
}
}

View File

@@ -18,30 +18,30 @@ private const val TAG = "CameraManager"
@SuppressLint("MissingPermission")
suspend fun CameraManager.openCamera(
cameraId: String,
onDisconnected: (camera: CameraDevice, reason: Throwable) -> Unit,
onDisconnected: (camera: CameraDevice, error: Throwable?) -> Unit,
queue: CameraQueues.CameraQueue
): CameraDevice =
suspendCancellableCoroutine { continuation ->
Log.i(TAG, "Camera $cameraId: Opening...")
Log.i(TAG, "Camera #$cameraId: Opening...")
val callback = object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
Log.i(TAG, "Camera $cameraId: Opened!")
Log.i(TAG, "Camera #$cameraId: Opened!")
continuation.resume(camera)
}
override fun onDisconnected(camera: CameraDevice) {
Log.i(TAG, "Camera $cameraId: Disconnected!")
Log.i(TAG, "Camera #$cameraId: Disconnected!")
if (continuation.isActive) {
continuation.resumeWithException(CameraCannotBeOpenedError(cameraId, CameraDeviceError.DISCONNECTED))
} else {
onDisconnected(camera, CameraDisconnectedError(cameraId, CameraDeviceError.DISCONNECTED))
onDisconnected(camera, null)
}
camera.close()
}
override fun onError(camera: CameraDevice, errorCode: Int) {
Log.e(TAG, "Camera $cameraId: Error! $errorCode")
Log.e(TAG, "Camera #$cameraId: Error! $errorCode")
val error = CameraDeviceError.fromCameraDeviceError(errorCode)
if (continuation.isActive) {
continuation.resumeWithException(CameraCannotBeOpenedError(cameraId, error))