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.util.Log
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
2023-09-01 05:08:33 -06:00
import com.mrousavy.camera.core.CameraDeviceDetails
2023-07-21 16:15:11 -06:00
import com.mrousavy.camera.frameprocessor.VisionCameraInstaller
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.*
2023-08-21 04:50:14 -06:00
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
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
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 ( )
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
2023-08-21 04:50:14 -06:00
private suspend fun findCameraView ( viewId : Int ) : CameraView {
return suspendCoroutine { continuation ->
UiThreadUtil . runOnUiThread {
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 ! " )
if ( view != null ) continuation . resume ( view )
else continuation . resumeWithException ( ViewNotFoundError ( viewId ) )
}
}
2022-07-20 06:02:20 -06:00
}
2021-02-19 08:28:14 -07:00
2023-07-21 16:15:11 -06:00
@ReactMethod ( isBlockingSynchronousMethod = true )
fun installFrameProcessorBindings ( ) : Boolean {
return try {
2023-08-21 04:50:14 -06:00
val proxy = VisionCameraProxy ( reactApplicationContext )
2023-07-21 16:15:11 -06:00
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 {
2023-08-21 04:50:14 -06:00
val view = findCameraView ( viewTag )
2021-02-26 02:56:20 -07:00
withPromise ( promise ) {
view . takePhoto ( 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 ) {
2023-08-21 04:50:14 -06:00
val map = makeErrorMap ( " capture/unknown " , " An unknown error occurred while trying to start a video recording! ${error.message} " , 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 ) {
2023-08-21 04:50:14 -06:00
coroutineScope . launch {
withPromise ( promise ) {
val view = findCameraView ( viewTag )
view . pauseRecording ( )
return @withPromise null
}
2022-03-22 03:44:58 -06:00
}
}
@ReactMethod
fun resumeRecording ( viewTag : Int , promise : Promise ) {
2023-08-21 04:50:14 -06:00
coroutineScope . launch {
2022-03-22 03:44:58 -06:00
val view = findCameraView ( viewTag )
2023-08-21 04:50:14 -06:00
withPromise ( promise ) {
view . resumeRecording ( )
return @withPromise null
}
2022-03-22 03:44:58 -06:00
}
}
2021-02-26 02:56:20 -07:00
@ReactMethod
fun stopRecording ( viewTag : Int , promise : Promise ) {
2023-08-21 04:50:14 -06:00
coroutineScope . launch {
2021-02-26 02:56:20 -07:00
val view = findCameraView ( viewTag )
2023-08-21 04:50:14 -06:00
withPromise ( promise ) {
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 {
2023-08-21 04:50:14 -06:00
val view = findCameraView ( viewTag )
2021-02-26 02:56:20 -07:00
withPromise ( promise ) {
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 ) {
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 ->
2023-08-21 04:50:14 -06:00
val device = CameraDeviceDetails ( manager , cameraId )
2023-03-13 07:23:19 -06:00
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
}
2023-08-21 04:50:14 -06:00
private fun canRequestPermission ( permission : String ) : Boolean {
val activity = currentActivity as ? PermissionAwareActivity
return activity ?. shouldShowRequestPermissionRationale ( permission ) ?: false
}
2021-02-26 02:56:20 -07:00
@ReactMethod
fun getCameraPermissionStatus ( promise : Promise ) {
val status = ContextCompat . checkSelfPermission ( reactApplicationContext , Manifest . permission . CAMERA )
2023-08-21 04:50:14 -06:00
var parsed = PermissionStatus . fromPermissionStatus ( status )
if ( parsed == PermissionStatus . DENIED && canRequestPermission ( Manifest . permission . CAMERA ) ) {
parsed = PermissionStatus . NOT _DETERMINED
}
promise . resolve ( parsed . unionValue )
2021-02-26 02:56:20 -07:00
}
@ReactMethod
fun getMicrophonePermissionStatus ( promise : Promise ) {
val status = ContextCompat . checkSelfPermission ( reactApplicationContext , Manifest . permission . RECORD _AUDIO )
2023-08-21 04:50:14 -06:00
var parsed = PermissionStatus . fromPermissionStatus ( status )
if ( parsed == PermissionStatus . DENIED && canRequestPermission ( Manifest . permission . RECORD _AUDIO ) ) {
parsed = PermissionStatus . NOT _DETERMINED
}
promise . resolve ( parsed . unionValue )
2021-02-26 02:56:20 -07:00
}
@ReactMethod
fun requestCameraPermission ( promise : Promise ) {
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
2023-08-21 04:50:14 -06:00
val parsed = PermissionStatus . fromPermissionStatus ( permissionStatus )
promise . resolve ( parsed . unionValue )
2021-02-26 02:56:20 -07:00
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 ) {
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
2023-08-21 04:50:14 -06:00
val parsed = PermissionStatus . fromPermissionStatus ( permissionStatus )
promise . resolve ( parsed . unionValue )
2021-02-26 02:56:20 -07:00
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
}