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.pm.PackageManager
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-10-13 10:33:20 -06:00
import com.mrousavy.camera.core.CameraError
import com.mrousavy.camera.core.ViewNotFoundError
2023-07-21 16:15:11 -06:00
import com.mrousavy.camera.frameprocessor.VisionCameraInstaller
import com.mrousavy.camera.frameprocessor.VisionCameraProxy
2023-10-17 03:49:04 -06:00
import com.mrousavy.camera.types.*
2021-02-26 02:56:20 -07:00
import com.mrousavy.camera.utils.*
2023-08-21 04:50:14 -06:00
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
2023-09-21 03:20:33 -06:00
import kotlinx.coroutines.*
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-09-21 03:20:33 -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 "
2023-09-21 03:20:33 -06:00
var sharedRequestCode = 10
2021-02-26 02:56:20 -07:00
}
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
}
}
2023-09-21 03:20:33 -06:00
override fun getName ( ) : String = TAG
2021-02-19 08:28:14 -07:00
2023-09-21 03:20:33 -06:00
private suspend fun findCameraView ( viewId : Int ) : CameraView =
suspendCoroutine { continuation ->
2023-08-21 04:50:14 -06:00
UiThreadUtil . runOnUiThread {
Log . d ( TAG , " Finding view $viewId ... " )
2023-09-21 03:20:33 -06:00
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 ) )
}
2023-08-21 04:50:14 -06:00
}
}
2021-02-19 08:28:14 -07:00
2023-07-21 16:15:11 -06:00
@ReactMethod ( isBlockingSynchronousMethod = true )
2023-09-21 03:20:33 -06:00
fun installFrameProcessorBindings ( ) : Boolean =
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
2023-11-27 09:20:26 -07:00
fun startRecording ( viewTag : Int , jsOptions : 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 {
2023-11-27 09:20:26 -07:00
val options = RecordVideoOptions ( jsOptions )
2021-06-07 05:08:40 -06:00
view . startRecording ( options , onRecordCallback )
} catch ( error : CameraError ) {
val map = makeErrorMap ( " ${error.domain} / ${error.id} " , error . message , error )
onRecordCallback ( null , map )
} catch ( error : Throwable ) {
2023-09-21 03:20:33 -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
2023-08-21 04:50:14 -06:00
private fun canRequestPermission ( permission : String ) : Boolean {
val activity = currentActivity as ? PermissionAwareActivity
return activity ?. shouldShowRequestPermissionRationale ( permission ) ?: false
}
2023-12-19 06:22:04 -07:00
@ReactMethod ( isBlockingSynchronousMethod = true )
fun getCameraPermissionStatus ( ) : String {
2021-02-26 02:56:20 -07:00
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
}
2023-12-19 06:22:04 -07:00
return parsed . unionValue
2021-02-26 02:56:20 -07:00
}
2023-12-19 06:22:04 -07:00
@ReactMethod ( isBlockingSynchronousMethod = true )
fun getMicrophonePermissionStatus ( ) : String {
2021-02-26 02:56:20 -07:00
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
}
2023-12-19 06:22:04 -07:00
return parsed . unionValue
2021-02-26 02:56:20 -07:00
}
@ReactMethod
fun requestCameraPermission ( promise : Promise ) {
val activity = reactApplicationContext . currentActivity
if ( activity is PermissionAwareActivity ) {
2023-09-21 03:20:33 -06:00
val currentRequestCode = sharedRequestCode ++
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 ) {
2023-09-21 03:20:33 -06:00
val currentRequestCode = sharedRequestCode ++
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
}