feat: New JS API for useCameraDevice and useCameraFormat and much faster getAvailableCameraDevices() (#1784)

* Update podfile

* Update useCameraFormat.ts

* Update API

* Delete FormatFilter.md

* Format CameraViewManager.m ObjC style

* Make `getAvailableCameraDevices` synchronous/blocking

* Create some docs

* fix: Fix HardwareLevel types

* fix: Use new device/format API

* Use 60 FPS format as an example

* Replace `Camera.getAvailableCameraDevices` with new `CameraDevices` API/Module

* Fix Lint

* KTLint options

* Use continuation indent of 8

* Use 2 spaces for indent

* Update .editorconfig

* Format code

* Update .editorconfig

* Format more

* Update VideoStabilizationMode.kt

* fix: Expose `CameraDevicesManager` to ObjC

* Update CameraPage.tsx

* fix: `requiresMainQueueSetup() -> false`

* Always prefer higher resolution

* Update CameraDevicesManager.swift

* Update CameraPage.tsx

* Also filter pixelFormat

* fix: Add AVFoundation import
This commit is contained in:
Marc Rousavy
2023-09-21 11:20:33 +02:00
committed by GitHub
parent 9eed89aac6
commit 977b859e46
61 changed files with 1110 additions and 815 deletions

View File

@@ -12,42 +12,37 @@ import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
suspend fun CameraCaptureSession.capture(captureRequest: CaptureRequest, enableShutterSound: Boolean): TotalCaptureResult {
return suspendCoroutine { continuation ->
this.capture(captureRequest, object: CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
super.onCaptureCompleted(session, request, result)
suspend fun CameraCaptureSession.capture(captureRequest: CaptureRequest, enableShutterSound: Boolean): TotalCaptureResult =
suspendCoroutine { continuation ->
this.capture(
captureRequest,
object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
continuation.resume(result)
}
override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
super.onCaptureStarted(session, request, timestamp, frameNumber)
if (enableShutterSound) {
val mediaActionSound = MediaActionSound()
mediaActionSound.play(MediaActionSound.SHUTTER_CLICK)
continuation.resume(result)
}
}
override fun onCaptureFailed(
session: CameraCaptureSession,
request: CaptureRequest,
failure: CaptureFailure
) {
super.onCaptureFailed(session, request, failure)
val wasImageCaptured = failure.wasImageCaptured()
val error = when (failure.reason) {
CaptureFailure.REASON_ERROR -> UnknownCaptureError(wasImageCaptured)
CaptureFailure.REASON_FLUSHED -> CaptureAbortedError(wasImageCaptured)
else -> UnknownCaptureError(wasImageCaptured)
override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
super.onCaptureStarted(session, request, timestamp, frameNumber)
if (enableShutterSound) {
val mediaActionSound = MediaActionSound()
mediaActionSound.play(MediaActionSound.SHUTTER_CLICK)
}
}
continuation.resumeWithException(error)
}
}, CameraQueues.cameraQueue.handler)
override fun onCaptureFailed(session: CameraCaptureSession, request: CaptureRequest, failure: CaptureFailure) {
super.onCaptureFailed(session, request, failure)
val wasImageCaptured = failure.wasImageCaptured()
val error = when (failure.reason) {
CaptureFailure.REASON_ERROR -> UnknownCaptureError(wasImageCaptured)
CaptureFailure.REASON_FLUSHED -> CaptureAbortedError(wasImageCaptured)
else -> UnknownCaptureError(wasImageCaptured)
}
continuation.resumeWithException(error)
}
},
CameraQueues.cameraQueue.handler
)
}
}

View File

@@ -8,29 +8,33 @@ import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.mrousavy.camera.CameraQueues
import com.mrousavy.camera.CameraSessionCannotBeConfiguredError
import com.mrousavy.camera.core.outputs.CameraOutputs
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
private const val TAG = "CreateCaptureSession"
private var sessionId = 1000
suspend fun CameraDevice.createCaptureSession(cameraManager: CameraManager,
outputs: CameraOutputs,
onClosed: (session: CameraCaptureSession) -> Unit,
queue: CameraQueues.CameraQueue): CameraCaptureSession {
return suspendCancellableCoroutine { continuation ->
suspend fun CameraDevice.createCaptureSession(
cameraManager: CameraManager,
outputs: CameraOutputs,
onClosed: (session: CameraCaptureSession) -> Unit,
queue: CameraQueues.CameraQueue
): CameraCaptureSession =
suspendCancellableCoroutine { continuation ->
val characteristics = cameraManager.getCameraCharacteristics(id)
val hardwareLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)!!
val sessionId = sessionId++
Log.i(TAG, "Camera $id: Creating Capture Session #$sessionId... " +
"Hardware Level: $hardwareLevel} | Outputs: $outputs")
Log.i(
TAG,
"Camera $id: Creating Capture Session #$sessionId... " +
"Hardware Level: $hardwareLevel} | Outputs: $outputs"
)
val callback = object: CameraCaptureSession.StateCallback() {
val callback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
Log.i(TAG, "Camera $id: Capture Session #$sessionId configured!")
continuation.resume(session)
@@ -78,4 +82,3 @@ suspend fun CameraDevice.createCaptureSession(cameraManager: CameraManager,
this.createCaptureSessionByOutputConfigurations(outputConfigurations, callback, queue.handler)
}
}
}

View File

@@ -23,14 +23,16 @@ private fun supportsSnapshotCapture(cameraCharacteristics: CameraCharacteristics
return true
}
fun CameraDevice.createPhotoCaptureRequest(cameraManager: CameraManager,
surface: Surface,
zoom: Float,
qualityPrioritization: QualityPrioritization,
flashMode: Flash,
enableRedEyeReduction: Boolean,
enableAutoStabilization: Boolean,
orientation: Orientation): CaptureRequest {
fun CameraDevice.createPhotoCaptureRequest(
cameraManager: CameraManager,
surface: Surface,
zoom: Float,
qualityPrioritization: QualityPrioritization,
flashMode: Flash,
enableRedEyeReduction: Boolean,
enableAutoStabilization: Boolean,
orientation: Orientation
): CaptureRequest {
val cameraCharacteristics = cameraManager.getCameraCharacteristics(this.id)
val template = if (qualityPrioritization == QualityPrioritization.SPEED && supportsSnapshotCapture(cameraCharacteristics)) {

View File

@@ -9,20 +9,22 @@ import com.mrousavy.camera.CameraCannotBeOpenedError
import com.mrousavy.camera.CameraDisconnectedError
import com.mrousavy.camera.CameraQueues
import com.mrousavy.camera.parsers.CameraDeviceError
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
private const val TAG = "CameraManager"
@SuppressLint("MissingPermission")
suspend fun CameraManager.openCamera(cameraId: String,
onDisconnected: (camera: CameraDevice, reason: Throwable) -> Unit,
queue: CameraQueues.CameraQueue): CameraDevice {
return suspendCancellableCoroutine { continuation ->
suspend fun CameraManager.openCamera(
cameraId: String,
onDisconnected: (camera: CameraDevice, reason: Throwable) -> Unit,
queue: CameraQueues.CameraQueue
): CameraDevice =
suspendCancellableCoroutine { continuation ->
Log.i(TAG, "Camera $cameraId: Opening...")
val callback = object: CameraDevice.StateCallback() {
val callback = object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
Log.i(TAG, "Camera $cameraId: Opened!")
continuation.resume(camera)
@@ -56,4 +58,3 @@ suspend fun CameraManager.openCamera(cameraId: String,
this.openCamera(cameraId, callback, queue.handler)
}
}
}

View File

@@ -1,5 +1,3 @@
package com.mrousavy.camera.extensions
fun <T> List<T>.containsAny(elements: List<T>): Boolean {
return elements.any { element -> this.contains(element) }
}
fun <T> List<T>.containsAny(elements: List<T>): Boolean = elements.any { element -> this.contains(element) }

View File

@@ -7,23 +7,21 @@ import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
fun List<Size>.closestToOrMax(size: Size?): Size {
return if (size != null) {
fun List<Size>.closestToOrMax(size: Size?): Size =
if (size != null) {
this.minBy { abs(it.width - size.width) + abs(it.height - size.height) }
} else {
this.maxBy { it.width * it.height }
}
}
fun Size.rotated(surfaceRotation: Int): Size {
return when (surfaceRotation) {
fun Size.rotated(surfaceRotation: Int): Size =
when (surfaceRotation) {
Surface.ROTATION_0 -> Size(width, height)
Surface.ROTATION_90 -> Size(height, width)
Surface.ROTATION_180 -> Size(width, height)
Surface.ROTATION_270 -> Size(height, width)
else -> Size(width, height)
}
}
val Size.bigger: Int
get() = max(width, height)
@@ -35,7 +33,4 @@ val SizeF.bigger: Float
val SizeF.smaller: Float
get() = min(this.width, this.height)
operator fun Size.compareTo(other: Size): Int {
return (this.width * this.height).compareTo(other.width * other.height)
}
operator fun Size.compareTo(other: Size): Int = (this.width * this.height).compareTo(other.width * other.height)