2021-02-19 20:41:49 +01:00
|
|
|
package com.mrousavy.camera
|
2021-02-19 16:28:14 +01:00
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.content.Context
|
2023-08-21 12:50:14 +02:00
|
|
|
import android.hardware.camera2.CameraManager
|
2021-02-19 16:28:14 +01:00
|
|
|
import android.util.Log
|
2023-08-23 15:39:24 +02:00
|
|
|
import android.view.ScaleGestureDetector
|
2021-02-19 16:28:14 +01:00
|
|
|
import android.widget.FrameLayout
|
2023-08-21 12:50:14 +02:00
|
|
|
import com.facebook.react.bridge.ReadableMap
|
2023-10-24 11:19:03 +02:00
|
|
|
import com.google.mlkit.vision.barcode.common.Barcode
|
|
|
|
import com.mrousavy.camera.core.CameraConfiguration
|
2023-10-13 18:33:20 +02:00
|
|
|
import com.mrousavy.camera.core.CameraQueues
|
2023-09-01 13:08:33 +02:00
|
|
|
import com.mrousavy.camera.core.CameraSession
|
2023-11-09 11:57:05 +01:00
|
|
|
import com.mrousavy.camera.core.CodeScannerFrame
|
2023-09-01 13:08:33 +02:00
|
|
|
import com.mrousavy.camera.core.PreviewView
|
2023-08-21 12:50:14 +02:00
|
|
|
import com.mrousavy.camera.extensions.installHierarchyFitter
|
2023-07-22 00:15:11 +02:00
|
|
|
import com.mrousavy.camera.frameprocessor.FrameProcessor
|
2023-10-24 11:19:03 +02:00
|
|
|
import com.mrousavy.camera.types.CameraDeviceFormat
|
2023-10-17 11:49:04 +02:00
|
|
|
import com.mrousavy.camera.types.CodeScannerOptions
|
|
|
|
import com.mrousavy.camera.types.Orientation
|
|
|
|
import com.mrousavy.camera.types.PixelFormat
|
|
|
|
import com.mrousavy.camera.types.ResizeMode
|
|
|
|
import com.mrousavy.camera.types.Torch
|
|
|
|
import com.mrousavy.camera.types.VideoStabilizationMode
|
2023-10-10 18:51:46 +02:00
|
|
|
import kotlin.coroutines.CoroutineContext
|
2023-08-21 12:50:14 +02:00
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
import kotlinx.coroutines.launch
|
2021-02-19 16:28:14 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// TODOs for the CameraView which are currently too hard to implement either because of CameraX' limitations, or my brain capacity.
|
|
|
|
//
|
|
|
|
// TODO: High-speed video recordings (export in CameraViewModule::getAvailableVideoDevices(), and set in CameraView::configurePreview()) (120FPS+)
|
|
|
|
// TODO: Better startRecording()/stopRecording() (promise + callback, wait for TurboModules/JSI)
|
|
|
|
// TODO: takePhoto() depth data
|
|
|
|
// TODO: takePhoto() raw capture
|
|
|
|
// TODO: takePhoto() return with jsi::Value Image reference for faster capture
|
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
@SuppressLint("ClickableViewAccessibility", "ViewConstructor", "MissingPermission")
|
2023-10-10 18:51:46 +02:00
|
|
|
class CameraView(context: Context) :
|
|
|
|
FrameLayout(context),
|
2023-10-24 11:19:03 +02:00
|
|
|
CoroutineScope,
|
|
|
|
CameraSession.CameraSessionCallback {
|
2021-08-25 11:33:57 +02:00
|
|
|
companion object {
|
|
|
|
const val TAG = "CameraView"
|
|
|
|
}
|
|
|
|
|
2021-02-26 10:56:20 +01:00
|
|
|
// react properties
|
|
|
|
// props that require reconfiguring
|
2023-08-21 12:50:14 +02:00
|
|
|
var cameraId: String? = null
|
2023-10-24 11:19:03 +02:00
|
|
|
set(value) {
|
|
|
|
if (value != null) {
|
|
|
|
// TODO: Move this into CameraSession
|
|
|
|
val f = if (format != null) CameraDeviceFormat.fromJSValue(format!!) else null
|
|
|
|
previewView.resizeToInputCamera(value, cameraManager, f)
|
|
|
|
}
|
|
|
|
field = value
|
|
|
|
}
|
2021-02-26 10:56:20 +01:00
|
|
|
var enableDepthData = false
|
2021-06-10 13:49:34 +02:00
|
|
|
var enableHighQualityPhotos: Boolean? = null
|
2021-02-26 10:56:20 +01:00
|
|
|
var enablePortraitEffectsMatteDelivery = false
|
2023-09-21 11:20:33 +02:00
|
|
|
|
2021-06-07 13:08:40 +02:00
|
|
|
// use-cases
|
|
|
|
var photo: Boolean? = null
|
|
|
|
var video: Boolean? = null
|
|
|
|
var audio: Boolean? = null
|
2021-07-12 15:16:03 +02:00
|
|
|
var enableFrameProcessor = false
|
2023-08-21 12:50:14 +02:00
|
|
|
var pixelFormat: PixelFormat = PixelFormat.NATIVE
|
2023-09-21 11:20:33 +02:00
|
|
|
|
2021-02-26 10:56:20 +01:00
|
|
|
// props that require format reconfiguring
|
|
|
|
var format: ReadableMap? = null
|
|
|
|
var fps: Int? = null
|
2023-08-21 12:50:14 +02:00
|
|
|
var videoStabilizationMode: VideoStabilizationMode? = null
|
2023-11-15 18:33:12 +01:00
|
|
|
var videoHdr = false
|
|
|
|
var photoHdr = false
|
2021-02-26 10:56:20 +01:00
|
|
|
var lowLightBoost: Boolean? = null // nullable bool
|
2023-09-21 11:20:33 +02:00
|
|
|
|
2021-02-26 10:56:20 +01:00
|
|
|
// other props
|
|
|
|
var isActive = false
|
2023-08-21 12:50:14 +02:00
|
|
|
var torch: Torch = Torch.OFF
|
2021-07-29 11:44:22 +02:00
|
|
|
var zoom: Float = 1f // in "factor"
|
2023-11-19 15:26:43 +01:00
|
|
|
var exposure: Float = 1f // TODO: Implement exposure bias
|
2023-10-24 11:19:03 +02:00
|
|
|
var orientation: Orientation = Orientation.PORTRAIT
|
2023-08-23 15:39:24 +02:00
|
|
|
var enableZoomGesture: Boolean = false
|
2023-10-24 11:19:03 +02:00
|
|
|
set(value) {
|
|
|
|
field = value
|
|
|
|
updateZoomGesture()
|
|
|
|
}
|
|
|
|
var resizeMode: ResizeMode = ResizeMode.COVER
|
|
|
|
set(value) {
|
|
|
|
previewView.resizeMode = value
|
|
|
|
field = value
|
|
|
|
}
|
2021-02-26 10:56:20 +01:00
|
|
|
|
2023-10-04 12:53:52 +02:00
|
|
|
// code scanner
|
2023-10-16 16:56:39 +02:00
|
|
|
var codeScannerOptions: CodeScannerOptions? = null
|
2023-10-04 12:53:52 +02:00
|
|
|
|
2021-02-26 10:56:20 +01:00
|
|
|
// private properties
|
2021-10-11 18:27:23 +02:00
|
|
|
private var isMounted = false
|
2023-08-21 12:50:14 +02:00
|
|
|
internal val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
2021-07-12 15:16:03 +02:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
// session
|
|
|
|
internal val cameraSession: CameraSession
|
2023-10-24 11:19:03 +02:00
|
|
|
private val previewView: PreviewView
|
2021-12-30 11:39:17 +01:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
internal var frameProcessor: FrameProcessor? = null
|
|
|
|
set(value) {
|
|
|
|
field = value
|
2023-09-01 12:20:17 +02:00
|
|
|
cameraSession.frameProcessor = frameProcessor
|
2022-01-04 16:57:40 +01:00
|
|
|
}
|
2021-07-26 11:32:58 +02:00
|
|
|
|
2023-10-10 18:51:46 +02:00
|
|
|
override val coroutineContext: CoroutineContext = CameraQueues.cameraQueue.coroutineDispatcher
|
|
|
|
|
2021-02-26 10:56:20 +01:00
|
|
|
init {
|
2023-08-21 12:50:14 +02:00
|
|
|
this.installHierarchyFitter()
|
2023-10-14 13:17:05 +02:00
|
|
|
clipToOutline = true
|
2023-10-24 11:19:03 +02:00
|
|
|
cameraSession = CameraSession(context, cameraManager, this)
|
|
|
|
previewView = cameraSession.createPreviewView(context)
|
|
|
|
addView(previewView)
|
2021-02-26 10:56:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onAttachedToWindow() {
|
2021-10-11 18:27:23 +02:00
|
|
|
if (!isMounted) {
|
|
|
|
isMounted = true
|
|
|
|
invokeOnViewReady()
|
|
|
|
}
|
2023-10-24 11:19:03 +02:00
|
|
|
update()
|
|
|
|
super.onAttachedToWindow()
|
2021-02-26 10:56:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDetachedFromWindow() {
|
2023-10-24 11:19:03 +02:00
|
|
|
update()
|
2021-02-26 10:56:20 +01:00
|
|
|
super.onDetachedFromWindow()
|
|
|
|
}
|
|
|
|
|
2023-11-18 14:58:07 +01:00
|
|
|
fun destroy() {
|
|
|
|
cameraSession.close()
|
|
|
|
}
|
|
|
|
|
2023-10-24 11:19:03 +02:00
|
|
|
fun update() {
|
|
|
|
Log.i(TAG, "Updating CameraSession...")
|
2023-10-10 19:18:54 +02:00
|
|
|
|
|
|
|
launch {
|
2023-10-24 11:19:03 +02:00
|
|
|
cameraSession.configure { config ->
|
|
|
|
// Input Camera Device
|
|
|
|
config.cameraId = cameraId
|
|
|
|
|
|
|
|
// Photo
|
|
|
|
if (photo == true) {
|
|
|
|
config.photo = CameraConfiguration.Output.Enabled.create(CameraConfiguration.Photo(Unit))
|
|
|
|
} else {
|
|
|
|
config.photo = CameraConfiguration.Output.Disabled.create()
|
2023-10-10 18:51:46 +02:00
|
|
|
}
|
2023-10-24 11:19:03 +02:00
|
|
|
|
|
|
|
// Video/Frame Processor
|
|
|
|
if (video == true || enableFrameProcessor) {
|
|
|
|
config.video = CameraConfiguration.Output.Enabled.create(
|
|
|
|
CameraConfiguration.Video(
|
|
|
|
pixelFormat,
|
|
|
|
enableFrameProcessor
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
config.video = CameraConfiguration.Output.Disabled.create()
|
2023-10-10 18:51:46 +02:00
|
|
|
}
|
2023-10-24 11:19:03 +02:00
|
|
|
|
|
|
|
// Audio
|
|
|
|
if (audio == true) {
|
|
|
|
config.audio = CameraConfiguration.Output.Enabled.create(CameraConfiguration.Audio(Unit))
|
|
|
|
} else {
|
|
|
|
config.audio = CameraConfiguration.Output.Disabled.create()
|
2023-10-10 18:51:46 +02:00
|
|
|
}
|
2023-10-24 11:19:03 +02:00
|
|
|
|
|
|
|
// Code Scanner
|
|
|
|
val codeScanner = codeScannerOptions
|
|
|
|
if (codeScanner != null) {
|
|
|
|
config.codeScanner = CameraConfiguration.Output.Enabled.create(
|
|
|
|
CameraConfiguration.CodeScanner(codeScanner.codeTypes)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
config.codeScanner = CameraConfiguration.Output.Disabled.create()
|
2023-10-10 18:51:46 +02:00
|
|
|
}
|
2021-02-26 10:56:20 +01:00
|
|
|
|
2023-10-24 11:19:03 +02:00
|
|
|
// Orientation
|
|
|
|
config.orientation = orientation
|
2021-07-07 12:57:28 +02:00
|
|
|
|
2023-11-15 18:33:12 +01:00
|
|
|
// HDR
|
|
|
|
config.videoHdr = videoHdr
|
|
|
|
config.photoHdr = photoHdr
|
|
|
|
|
2023-10-24 11:19:03 +02:00
|
|
|
// Format
|
|
|
|
val format = format
|
|
|
|
if (format != null) {
|
|
|
|
config.format = CameraDeviceFormat.fromJSValue(format)
|
|
|
|
} else {
|
|
|
|
config.format = null
|
|
|
|
}
|
2021-02-26 10:56:20 +01:00
|
|
|
|
2023-10-24 11:19:03 +02:00
|
|
|
// Side-Props
|
|
|
|
config.fps = fps
|
|
|
|
config.enableLowLightBoost = lowLightBoost ?: false
|
|
|
|
config.torch = torch
|
2023-11-19 15:26:43 +01:00
|
|
|
config.exposure = exposure
|
2021-12-30 11:39:17 +01:00
|
|
|
|
2023-10-24 11:19:03 +02:00
|
|
|
// Zoom
|
|
|
|
config.zoom = zoom
|
2021-12-30 11:39:17 +01:00
|
|
|
|
2023-10-24 11:19:03 +02:00
|
|
|
// isActive
|
|
|
|
config.isActive = isActive && isAttachedToWindow
|
|
|
|
}
|
2023-08-21 12:50:14 +02:00
|
|
|
}
|
|
|
|
}
|
2021-02-26 10:56:20 +01:00
|
|
|
|
2023-08-23 15:39:24 +02:00
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
|
|
private fun updateZoomGesture() {
|
|
|
|
if (enableZoomGesture) {
|
2023-09-21 11:20:33 +02:00
|
|
|
val scaleGestureDetector = ScaleGestureDetector(
|
|
|
|
context,
|
|
|
|
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
|
|
|
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
|
|
|
zoom *= detector.scaleFactor
|
2023-10-24 11:19:03 +02:00
|
|
|
update()
|
2023-09-21 11:20:33 +02:00
|
|
|
return true
|
|
|
|
}
|
2023-08-23 15:39:24 +02:00
|
|
|
}
|
2023-09-21 11:20:33 +02:00
|
|
|
)
|
2023-08-23 15:39:24 +02:00
|
|
|
setOnTouchListener { _, event ->
|
|
|
|
scaleGestureDetector.onTouchEvent(event)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
setOnTouchListener(null)
|
|
|
|
}
|
|
|
|
}
|
2023-10-24 11:19:03 +02:00
|
|
|
|
|
|
|
override fun onError(error: Throwable) {
|
|
|
|
invokeOnError(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onInitialized() {
|
|
|
|
invokeOnInitialized()
|
|
|
|
}
|
|
|
|
|
2023-11-09 11:57:05 +01:00
|
|
|
override fun onCodeScanned(codes: List<Barcode>, scannerFrame: CodeScannerFrame) {
|
|
|
|
invokeOnCodeScanned(codes, scannerFrame)
|
2023-10-24 11:19:03 +02:00
|
|
|
}
|
2021-02-19 16:28:14 +01:00
|
|
|
}
|