2021-02-19 12:41:49 -07:00
package com.mrousavy.camera
2021-02-19 08:28:14 -07:00
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.camera2.CameraManager
import android.os.Build
import android.util.Log
2021-03-12 02:45:23 -07:00
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.lifecycle.ProcessCameraProvider
2021-02-19 08:28:14 -07:00
import androidx.core.content.ContextCompat
import com.facebook.react.bridge.*
2022-01-02 08:02:17 -07:00
import com.facebook.react.module.annotations.ReactModule
2021-02-19 08:28:14 -07:00
import com.facebook.react.modules.core.PermissionAwareActivity
import com.facebook.react.modules.core.PermissionListener
2022-07-20 06:02:20 -06:00
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.bridge.ReactApplicationContext
2023-07-21 16:15:11 -06:00
import com.mrousavy.camera.frameprocessor.VisionCameraInstaller
2022-07-20 06:02:20 -06:00
import java.util.concurrent.ExecutorService
2023-07-21 16:15:11 -06:00
import com.mrousavy.camera.frameprocessor.VisionCameraProxy
2021-02-26 02:56:20 -07:00
import com.mrousavy.camera.parsers.*
import com.mrousavy.camera.utils.*
2021-03-12 02:45:23 -07:00
import kotlinx.coroutines.*
import kotlinx.coroutines.guava.await
2022-01-02 08:02:17 -07:00
import java.util.concurrent.Executors
2021-02-19 08:28:14 -07:00
2022-01-02 08:02:17 -07:00
@ReactModule ( name = CameraViewModule . TAG )
2021-08-25 03:33:57 -06:00
@Suppress ( " unused " )
2023-07-21 16:15:11 -06:00
class CameraViewModule ( reactContext : ReactApplicationContext ) : ReactContextBaseJavaModule ( reactContext ) {
2021-02-26 02:56:20 -07:00
companion object {
2021-07-07 07:00:32 -06:00
const val TAG = " CameraView "
2021-02-26 02:56:20 -07:00
var RequestCode = 10
}
2021-02-19 08:28:14 -07:00
2022-01-02 08:02:17 -07:00
var frameProcessorThread : ExecutorService = Executors . newSingleThreadExecutor ( )
2021-08-25 03:33:57 -06:00
private val coroutineScope = CoroutineScope ( Dispatchers . Default ) // TODO: or Dispatchers.Main?
2021-06-27 04:37:54 -06:00
2023-07-21 16:15:11 -06:00
override fun invalidate ( ) {
super . invalidate ( )
frameProcessorThread . shutdown ( )
2021-08-25 03:33:57 -06:00
if ( coroutineScope . isActive ) {
coroutineScope . cancel ( " CameraViewModule has been destroyed. " )
2021-06-27 04:37:54 -06:00
}
}
2021-02-26 02:56:20 -07:00
override fun getName ( ) : String {
2021-07-07 07:00:32 -06:00
return TAG
2021-02-26 02:56:20 -07:00
}
2021-02-19 08:28:14 -07:00
2022-07-20 06:02:20 -06:00
private fun findCameraView ( viewId : Int ) : CameraView {
Log . d ( TAG , " Finding view $viewId ... " )
val view = if ( reactApplicationContext != null ) UIManagerHelper . getUIManager ( reactApplicationContext , viewId ) ?. resolveView ( viewId ) as CameraView ? else null
Log . d ( TAG , if ( reactApplicationContext != null ) " Found view $viewId ! " else " Couldn't find view $viewId ! " )
return view ?: throw ViewNotFoundError ( viewId )
}
2021-02-19 08:28:14 -07:00
2023-07-21 16:15:11 -06:00
@ReactMethod ( isBlockingSynchronousMethod = true )
fun installFrameProcessorBindings ( ) : Boolean {
return try {
val proxy = VisionCameraProxy ( reactApplicationContext , frameProcessorThread )
VisionCameraInstaller . install ( proxy )
true
} catch ( e : Error ) {
Log . e ( TAG , " Failed to install Frame Processor JSI Bindings! " , e )
false
}
}
2021-02-26 02:56:20 -07:00
@ReactMethod
fun takePhoto ( viewTag : Int , options : ReadableMap , promise : Promise ) {
2021-08-25 03:33:57 -06:00
coroutineScope . launch {
2021-02-26 02:56:20 -07:00
withPromise ( promise ) {
val view = findCameraView ( viewTag )
view . takePhoto ( options )
}
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
}
2021-08-25 03:33:57 -06:00
@Suppress ( " unused " )
2021-02-26 02:56:20 -07:00
@ReactMethod
fun takeSnapshot ( viewTag : Int , options : ReadableMap , promise : Promise ) {
2021-08-25 03:33:57 -06:00
coroutineScope . launch {
2021-02-26 02:56:20 -07:00
withPromise ( promise ) {
val view = findCameraView ( viewTag )
view . takeSnapshot ( options )
}
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
}
// TODO: startRecording() cannot be awaited, because I can't have a Promise and a onRecordedCallback in the same function. Hopefully TurboModules allows that
2021-06-07 05:08:40 -06:00
@ReactMethod
2021-02-26 02:56:20 -07:00
fun startRecording ( viewTag : Int , options : ReadableMap , onRecordCallback : Callback ) {
2021-08-25 03:33:57 -06:00
coroutineScope . launch {
2021-02-26 02:56:20 -07:00
val view = findCameraView ( viewTag )
2021-06-07 05:08:40 -06:00
try {
view . startRecording ( options , onRecordCallback )
} catch ( error : CameraError ) {
val map = makeErrorMap ( " ${error.domain} / ${error.id} " , error . message , error )
onRecordCallback ( null , map )
} catch ( error : Throwable ) {
2021-06-27 04:37:54 -06:00
val map = makeErrorMap ( " capture/unknown " , " An unknown error occurred while trying to start a video recording! " , error )
2021-06-07 05:08:40 -06:00
onRecordCallback ( null , map )
}
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
}
2022-03-22 03:44:58 -06:00
@ReactMethod
fun pauseRecording ( viewTag : Int , promise : Promise ) {
withPromise ( promise ) {
val view = findCameraView ( viewTag )
view . pauseRecording ( )
return @withPromise null
}
}
@ReactMethod
fun resumeRecording ( viewTag : Int , promise : Promise ) {
withPromise ( promise ) {
val view = findCameraView ( viewTag )
view . resumeRecording ( )
return @withPromise null
}
}
2021-02-26 02:56:20 -07:00
@ReactMethod
fun stopRecording ( viewTag : Int , promise : Promise ) {
withPromise ( promise ) {
val view = findCameraView ( viewTag )
view . stopRecording ( )
return @withPromise null
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
}
@ReactMethod
fun focus ( viewTag : Int , point : ReadableMap , promise : Promise ) {
2021-08-25 03:33:57 -06:00
coroutineScope . launch {
2021-02-26 02:56:20 -07:00
withPromise ( promise ) {
val view = findCameraView ( viewTag )
view . focus ( point )
return @withPromise null
}
}
}
2021-02-19 08:28:14 -07:00
2021-02-26 02:56:20 -07:00
@ReactMethod
fun getAvailableCameraDevices ( promise : Promise ) {
2021-08-25 03:33:57 -06:00
coroutineScope . launch {
2021-03-12 02:45:23 -07:00
withPromise ( promise ) {
2021-07-07 04:57:28 -06:00
val cameraProvider = ProcessCameraProvider . getInstance ( reactApplicationContext ) . await ( )
2021-12-30 03:39:17 -07:00
val extensionsManager = ExtensionsManager . getInstanceAsync ( reactApplicationContext , cameraProvider ) . await ( )
2023-03-13 07:23:19 -06:00
val manager = reactApplicationContext . getSystemService ( Context . CAMERA _SERVICE ) as CameraManager
2021-03-12 02:45:23 -07:00
2023-03-13 07:23:19 -06:00
val devices = Arguments . createArray ( )
manager . cameraIdList . forEach { cameraId ->
val device = CameraDevice ( manager , extensionsManager , cameraId )
devices . pushMap ( device . toMap ( ) )
2021-02-19 08:28:14 -07:00
}
2023-03-13 07:23:19 -06:00
promise . resolve ( devices )
2021-02-26 02:56:20 -07:00
}
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
}
@ReactMethod
fun getCameraPermissionStatus ( promise : Promise ) {
val status = ContextCompat . checkSelfPermission ( reactApplicationContext , Manifest . permission . CAMERA )
promise . resolve ( parsePermissionStatus ( status ) )
}
@ReactMethod
fun getMicrophonePermissionStatus ( promise : Promise ) {
val status = ContextCompat . checkSelfPermission ( reactApplicationContext , Manifest . permission . RECORD _AUDIO )
promise . resolve ( parsePermissionStatus ( status ) )
}
@ReactMethod
fun requestCameraPermission ( promise : Promise ) {
2021-08-09 03:43:56 -06:00
if ( Build . VERSION . SDK _INT < Build . VERSION_CODES . M ) {
// API 21 and below always grants permission on app install
return promise . resolve ( " authorized " )
}
2021-02-26 02:56:20 -07:00
val activity = reactApplicationContext . currentActivity
if ( activity is PermissionAwareActivity ) {
2021-04-20 02:21:59 -06:00
val currentRequestCode = RequestCode ++
2021-02-26 02:56:20 -07:00
val listener = PermissionListener { requestCode : Int , _ : Array < String > , grantResults : IntArray ->
if ( requestCode == currentRequestCode ) {
2021-04-26 07:39:20 -06:00
val permissionStatus = if ( grantResults . isNotEmpty ( ) ) grantResults [ 0 ] else PackageManager . PERMISSION _DENIED
2021-02-26 02:56:20 -07:00
promise . resolve ( parsePermissionStatus ( permissionStatus ) )
return @PermissionListener true
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
return @PermissionListener false
}
activity . requestPermissions ( arrayOf ( Manifest . permission . CAMERA ) , currentRequestCode , listener )
} else {
promise . reject ( " NO_ACTIVITY " , " No PermissionAwareActivity was found! Make sure the app has launched before calling this function. " )
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
}
@ReactMethod
fun requestMicrophonePermission ( promise : Promise ) {
2021-08-09 03:43:56 -06:00
if ( Build . VERSION . SDK _INT < Build . VERSION_CODES . M ) {
// API 21 and below always grants permission on app install
return promise . resolve ( " authorized " )
}
2021-02-26 02:56:20 -07:00
val activity = reactApplicationContext . currentActivity
if ( activity is PermissionAwareActivity ) {
2021-04-20 02:21:59 -06:00
val currentRequestCode = RequestCode ++
2021-02-26 02:56:20 -07:00
val listener = PermissionListener { requestCode : Int , _ : Array < String > , grantResults : IntArray ->
if ( requestCode == currentRequestCode ) {
2021-04-26 07:39:20 -06:00
val permissionStatus = if ( grantResults . isNotEmpty ( ) ) grantResults [ 0 ] else PackageManager . PERMISSION _DENIED
2021-02-26 02:56:20 -07:00
promise . resolve ( parsePermissionStatus ( permissionStatus ) )
return @PermissionListener true
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
return @PermissionListener false
}
activity . requestPermissions ( arrayOf ( Manifest . permission . RECORD _AUDIO ) , currentRequestCode , listener )
} else {
promise . reject ( " NO_ACTIVITY " , " No PermissionAwareActivity was found! Make sure the app has launched before calling this function. " )
2021-02-19 08:28:14 -07:00
}
2021-02-26 02:56:20 -07:00
}
2021-02-19 08:28:14 -07:00
}