Skip to content

Commit d31b115

Browse files
committed
fix: Make sure that camera is initialized when getting devices
1 parent 0d7b939 commit d31b115

1 file changed

Lines changed: 25 additions & 13 deletions

File tree

package/android/src/main/java/com/mrousavy/camera/react/CameraDevicesManager.kt

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,26 @@ import com.facebook.react.bridge.ReadableArray
1313
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
1414
import com.mrousavy.camera.core.CameraDeviceDetails
1515
import com.mrousavy.camera.core.CameraQueues
16-
import com.mrousavy.camera.core.extensions.await
17-
import kotlinx.coroutines.CoroutineScope
18-
import kotlinx.coroutines.asCoroutineDispatcher
19-
import kotlinx.coroutines.launch
16+
import java.util.concurrent.Callable
17+
import java.util.concurrent.Future
2018

2119
class CameraDevicesManager(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
2220
companion object {
2321
private const val TAG = "CameraDevices"
2422
}
2523
private val executor = CameraQueues.cameraExecutor
26-
private val coroutineScope = CoroutineScope(executor.asCoroutineDispatcher())
24+
25+
// Because getConstants() and initialize() are only called once for the entire life of the app process by react native.
26+
// We have to make sure that everything is initialized, otherwise no devices are going to be retrieved ever.
27+
// Either because getConstants() returns empty, or that the sendAvailableDevicesChangedEvent inside initialize() sends empty as well.
28+
// We still give the opportunity for device to be initialized between init call and react initialization.
29+
private val cameraInitializationFuture: Future<CameraInitialization>
2730
private val cameraManager = reactContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
28-
private var cameraProvider: ProcessCameraProvider? = null
29-
private var extensionsManager: ExtensionsManager? = null
31+
32+
private class CameraInitialization {
33+
var cameraProvider: ProcessCameraProvider? = null
34+
var extensionsManager: ExtensionsManager? = null
35+
}
3036

3137
private val callback = object : CameraManager.AvailabilityCallback() {
3238
private var deviceIds = cameraManager.cameraIdList.toMutableList()
@@ -61,20 +67,23 @@ class CameraDevicesManager(private val reactContext: ReactApplicationContext) :
6167

6268
// Init cameraProvider + manager as early as possible
6369
init {
64-
coroutineScope.launch {
70+
cameraInitializationFuture = executor.submit(Callable {
71+
val cameraInitialization = CameraInitialization()
6572
try {
6673
Log.i(TAG, "Initializing ProcessCameraProvider...")
67-
cameraProvider = ProcessCameraProvider.getInstance(reactContext).await(executor)
74+
cameraInitialization.cameraProvider = ProcessCameraProvider.getInstance(reactContext).get()
6875
Log.i(TAG, "Initializing ExtensionsManager...")
69-
extensionsManager = ExtensionsManager.getInstanceAsync(reactContext, cameraProvider!!).await(executor)
76+
cameraInitialization.extensionsManager = ExtensionsManager.getInstanceAsync(reactContext, cameraInitialization.cameraProvider!!).get()
7077
Log.i(TAG, "Successfully initialized!")
7178
} catch (error: Throwable) {
7279
Log.e(TAG, "Failed to initialize ProcessCameraProvider/ExtensionsManager! Error: ${error.message}", error)
7380
}
74-
}
81+
return@Callable cameraInitialization
82+
})
7583
}
7684

7785
// Note: initialize() will be called after getConstants on new arch!
86+
// This is not what I observed, it is called before for me. Potentially leading to an issue.
7887
override fun initialize() {
7988
super.initialize()
8089
cameraManager.registerAvailabilityCallback(callback, null)
@@ -88,8 +97,9 @@ class CameraDevicesManager(private val reactContext: ReactApplicationContext) :
8897

8998
private fun getDevicesJson(): ReadableArray {
9099
val devices = Arguments.createArray()
91-
val cameraProvider = cameraProvider ?: return devices
92-
val extensionsManager = extensionsManager ?: return devices
100+
101+
val cameraProvider = cameraInitializationFuture.get().cameraProvider ?: return devices
102+
val extensionsManager = cameraInitializationFuture.get().extensionsManager ?: return devices
93103

94104
cameraProvider.availableCameraInfos.forEach { cameraInfo ->
95105
val device = CameraDeviceDetails(cameraInfo, extensionsManager)
@@ -98,12 +108,14 @@ class CameraDevicesManager(private val reactContext: ReactApplicationContext) :
98108
return devices
99109
}
100110

111+
// Called by CameraManager.AvailabilityCallback registered after initialize()
101112
fun sendAvailableDevicesChangedEvent() {
102113
val eventEmitter = reactContext.getJSModule(RCTDeviceEventEmitter::class.java)
103114
val devices = getDevicesJson()
104115
eventEmitter.emit("CameraDevicesChanged", devices)
105116
}
106117

118+
// Called by react native only once
107119
override fun getConstants(): MutableMap<String, Any?> {
108120
val devices = getDevicesJson()
109121
val preferredDevice = if (devices.size() > 0) devices.getMap(0) else null

0 commit comments

Comments
 (0)