capacitor-camera-view 2.0.2 → 2.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -19
- package/android/build.gradle +9 -5
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +491 -116
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +181 -31
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraResult.kt +47 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +11 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/VideoRecordingQuality.kt +10 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +114 -5
- package/dist/docs.json +281 -8
- package/dist/esm/definitions.d.ts +128 -6
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +26 -4
- package/dist/esm/web.js +218 -18
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +219 -18
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +219 -18
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CameraViewPlugin/CameraError.swift +125 -2
- package/ios/Sources/CameraViewPlugin/CameraEvents.swift +109 -0
- package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +28 -1
- package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +30 -41
- package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +38 -7
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +4 -3
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +302 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +246 -166
- package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +194 -96
- package/ios/Sources/CameraViewPlugin/TempFileManager.swift +215 -0
- package/ios/Sources/CameraViewPlugin/Utils.swift +102 -0
- package/package.json +17 -17
|
@@ -12,12 +12,28 @@ import com.getcapacitor.annotation.CapacitorPlugin
|
|
|
12
12
|
import com.getcapacitor.annotation.Permission
|
|
13
13
|
import com.getcapacitor.annotation.PermissionCallback
|
|
14
14
|
import com.michaelwolz.capacitorcameraview.model.BarcodeDetectionResult
|
|
15
|
+
import com.michaelwolz.capacitorcameraview.model.VideoRecordingQuality
|
|
16
|
+
import kotlinx.coroutines.CoroutineScope
|
|
17
|
+
import kotlinx.coroutines.Dispatchers
|
|
18
|
+
import kotlinx.coroutines.Job
|
|
19
|
+
import kotlinx.coroutines.SupervisorJob
|
|
20
|
+
import kotlinx.coroutines.cancel
|
|
21
|
+
import kotlinx.coroutines.launch
|
|
15
22
|
|
|
16
23
|
@CapacitorPlugin(
|
|
17
24
|
name = "CameraView",
|
|
18
|
-
permissions = [
|
|
25
|
+
permissions = [
|
|
26
|
+
Permission(strings = [Manifest.permission.CAMERA], alias = "camera"),
|
|
27
|
+
Permission(strings = [Manifest.permission.RECORD_AUDIO], alias = "microphone")
|
|
28
|
+
]
|
|
19
29
|
)
|
|
20
30
|
class CameraViewPlugin : Plugin() {
|
|
31
|
+
// Coroutine scope for async operations
|
|
32
|
+
private val pluginScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
33
|
+
|
|
34
|
+
// Job for barcode event subscription
|
|
35
|
+
private var barcodeJob: Job? = null
|
|
36
|
+
|
|
21
37
|
private val implementation by lazy {
|
|
22
38
|
CameraView(this)
|
|
23
39
|
}
|
|
@@ -41,26 +57,42 @@ class CameraViewPlugin : Plugin() {
|
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
private fun startCamera(call: PluginCall) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
val config = sessionConfigFromPluginCall(call)
|
|
61
|
+
|
|
62
|
+
pluginScope.launch {
|
|
63
|
+
implementation.startSessionAsync(config).fold(
|
|
64
|
+
onSuccess = {
|
|
65
|
+
// Subscribe to barcode events if detection is enabled
|
|
66
|
+
if (config.enableBarcodeDetection) {
|
|
67
|
+
barcodeJob?.cancel()
|
|
68
|
+
barcodeJob = pluginScope.launch {
|
|
69
|
+
implementation.barcodeEvents.collect { result ->
|
|
70
|
+
notifyBarcodeDetected(result)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
50
74
|
call.resolve()
|
|
75
|
+
},
|
|
76
|
+
onError = { error ->
|
|
77
|
+
call.reject("Failed to start camera preview: ${error.localizedMessage}", error)
|
|
51
78
|
}
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
)
|
|
80
|
+
}
|
|
54
81
|
}
|
|
55
82
|
|
|
56
83
|
@PluginMethod
|
|
57
84
|
fun stop(call: PluginCall) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
// Cancel barcode subscription
|
|
86
|
+
barcodeJob?.cancel()
|
|
87
|
+
barcodeJob = null
|
|
88
|
+
|
|
89
|
+
pluginScope.launch {
|
|
90
|
+
implementation.stopSessionAsync().fold(
|
|
91
|
+
onSuccess = { call.resolve() },
|
|
92
|
+
onError = { error ->
|
|
93
|
+
call.reject("Failed to stop camera preview: ${error.localizedMessage}", error)
|
|
94
|
+
}
|
|
95
|
+
)
|
|
64
96
|
}
|
|
65
97
|
}
|
|
66
98
|
|
|
@@ -74,7 +106,7 @@ class CameraViewPlugin : Plugin() {
|
|
|
74
106
|
|
|
75
107
|
@PluginMethod
|
|
76
108
|
fun capture(call: PluginCall) {
|
|
77
|
-
val timeStart = System.currentTimeMillis()
|
|
109
|
+
val timeStart = System.currentTimeMillis()
|
|
78
110
|
val quality = call.getInt("quality") ?: 90
|
|
79
111
|
val saveToFile = call.getBoolean("saveToFile") ?: false
|
|
80
112
|
|
|
@@ -83,19 +115,23 @@ class CameraViewPlugin : Plugin() {
|
|
|
83
115
|
return
|
|
84
116
|
}
|
|
85
117
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
118
|
+
pluginScope.launch {
|
|
119
|
+
implementation.capturePhotoAsync(quality, saveToFile).fold(
|
|
120
|
+
onSuccess = { result ->
|
|
121
|
+
call.resolve(result)
|
|
122
|
+
Log.d(TAG, "capture took ${System.currentTimeMillis() - timeStart}ms")
|
|
123
|
+
},
|
|
124
|
+
onError = { error ->
|
|
125
|
+
call.reject("Failed to capture image: ${error.message}", error)
|
|
126
|
+
Log.d(TAG, "capture failed after ${System.currentTimeMillis() - timeStart}ms")
|
|
127
|
+
}
|
|
128
|
+
)
|
|
93
129
|
}
|
|
94
130
|
}
|
|
95
131
|
|
|
96
132
|
@PluginMethod
|
|
97
133
|
fun captureSample(call: PluginCall) {
|
|
98
|
-
val timeStart = System.currentTimeMillis()
|
|
134
|
+
val timeStart = System.currentTimeMillis()
|
|
99
135
|
val quality = call.getInt("quality") ?: 90
|
|
100
136
|
val saveToFile = call.getBoolean("saveToFile") ?: false
|
|
101
137
|
|
|
@@ -104,13 +140,123 @@ class CameraViewPlugin : Plugin() {
|
|
|
104
140
|
return
|
|
105
141
|
}
|
|
106
142
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
143
|
+
pluginScope.launch {
|
|
144
|
+
implementation.captureSampleFromPreviewAsync(quality, saveToFile).fold(
|
|
145
|
+
onSuccess = { result ->
|
|
146
|
+
call.resolve(result)
|
|
147
|
+
Log.d(TAG, "captureSample took ${System.currentTimeMillis() - timeStart}ms")
|
|
148
|
+
},
|
|
149
|
+
onError = { error ->
|
|
150
|
+
call.reject("Failed to capture frame: ${error.message}", error)
|
|
151
|
+
Log.d(
|
|
152
|
+
TAG,
|
|
153
|
+
"captureSample failed after ${System.currentTimeMillis() - timeStart}ms"
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@PluginMethod
|
|
161
|
+
override fun requestPermissions(call: PluginCall) {
|
|
162
|
+
val permissionsList = call.getArray("permissions")
|
|
163
|
+
?.toList<String>()
|
|
164
|
+
?: listOf("camera")
|
|
165
|
+
|
|
166
|
+
// Determine which aliases still need to be requested
|
|
167
|
+
val aliasesToRequest = permissionsList.filter { alias ->
|
|
168
|
+
getPermissionState(alias) != PermissionState.GRANTED
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (aliasesToRequest.isEmpty()) {
|
|
172
|
+
checkPermissions(call)
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Store which permissions to request so callback can continue the chain
|
|
177
|
+
call.data.put("_pendingAliases", com.getcapacitor.JSArray(aliasesToRequest))
|
|
178
|
+
requestPermissionForAlias(aliasesToRequest.first(), call, "requestedPermsCallback")
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@PermissionCallback
|
|
182
|
+
private fun requestedPermsCallback(call: PluginCall) {
|
|
183
|
+
val pendingAliases = call.getArray("_pendingAliases")?.toList<String>() ?: emptyList()
|
|
184
|
+
|
|
185
|
+
// Find remaining aliases that still need requesting
|
|
186
|
+
val remaining = pendingAliases.drop(1).filter { alias ->
|
|
187
|
+
getPermissionState(alias) != PermissionState.GRANTED
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (remaining.isNotEmpty()) {
|
|
191
|
+
call.data.put("_pendingAliases", com.getcapacitor.JSArray(remaining))
|
|
192
|
+
requestPermissionForAlias(remaining.first(), call, "requestedPermsCallback")
|
|
193
|
+
} else {
|
|
194
|
+
checkPermissions(call)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@PluginMethod
|
|
199
|
+
fun startRecording(call: PluginCall) {
|
|
200
|
+
val enableAudio = call.getBoolean("enableAudio") ?: false
|
|
201
|
+
val videoQuality =
|
|
202
|
+
parseVideoRecordingQuality(call.getString("videoQuality"))
|
|
203
|
+
?: run {
|
|
204
|
+
call.reject("Invalid videoQuality. Use one of: lowest, sd, hd, fhd, uhd, highest")
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (enableAudio && getPermissionState("microphone") != PermissionState.GRANTED) {
|
|
209
|
+
requestPermissionForAlias("microphone", call, "microphonePermsCallback")
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
doStartRecording(call, enableAudio, videoQuality)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@PermissionCallback
|
|
217
|
+
private fun microphonePermsCallback(call: PluginCall) {
|
|
218
|
+
if (getPermissionState("microphone") == PermissionState.GRANTED) {
|
|
219
|
+
val enableAudio = call.getBoolean("enableAudio") ?: false
|
|
220
|
+
val videoQuality =
|
|
221
|
+
parseVideoRecordingQuality(call.getString("videoQuality"))
|
|
222
|
+
?: run {
|
|
223
|
+
call.reject("Invalid videoQuality. Use one of: lowest, sd, hd, fhd, uhd, highest")
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
doStartRecording(call, enableAudio, videoQuality)
|
|
227
|
+
} else {
|
|
228
|
+
call.reject("Microphone permission is required for audio recording")
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Helper method to start recording after ensuring permissions are granted.
|
|
235
|
+
*/
|
|
236
|
+
private fun doStartRecording(
|
|
237
|
+
call: PluginCall,
|
|
238
|
+
enableAudio: Boolean,
|
|
239
|
+
videoQuality: VideoRecordingQuality
|
|
240
|
+
) {
|
|
241
|
+
pluginScope.launch {
|
|
242
|
+
implementation.startRecordingAsync(enableAudio, videoQuality).fold(
|
|
243
|
+
onSuccess = { call.resolve() },
|
|
244
|
+
onError = { error ->
|
|
245
|
+
call.reject("Failed to start recording: ${error.message}", error)
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@PluginMethod
|
|
252
|
+
fun stopRecording(call: PluginCall) {
|
|
253
|
+
pluginScope.launch {
|
|
254
|
+
implementation.stopRecordingAsync().fold(
|
|
255
|
+
onSuccess = { result -> call.resolve(result) },
|
|
256
|
+
onError = { error ->
|
|
257
|
+
call.reject("Failed to stop recording: ${error.message}", error)
|
|
258
|
+
}
|
|
259
|
+
)
|
|
114
260
|
}
|
|
115
261
|
}
|
|
116
262
|
|
|
@@ -268,6 +414,10 @@ class CameraViewPlugin : Plugin() {
|
|
|
268
414
|
}
|
|
269
415
|
|
|
270
416
|
override fun handleOnDestroy() {
|
|
417
|
+
// Cancel barcode subscription and plugin scope
|
|
418
|
+
barcodeJob?.cancel()
|
|
419
|
+
pluginScope.cancel()
|
|
420
|
+
|
|
271
421
|
implementation.cleanup()
|
|
272
422
|
super.handleOnDestroy()
|
|
273
423
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview.model
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A Result type for consistent error handling throughout camera operations.
|
|
5
|
+
* Provides a functional approach to handling success and error cases.
|
|
6
|
+
*/
|
|
7
|
+
sealed class CameraResult<out T> {
|
|
8
|
+
data class Success<T>(val value: T) : CameraResult<T>()
|
|
9
|
+
data class Error(val exception: Exception) : CameraResult<Nothing>()
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Transforms this result using the provided functions.
|
|
13
|
+
*/
|
|
14
|
+
inline fun <R> fold(
|
|
15
|
+
onSuccess: (T) -> R,
|
|
16
|
+
onError: (Exception) -> R
|
|
17
|
+
): R = when (this) {
|
|
18
|
+
is Success -> onSuccess(value)
|
|
19
|
+
is Error -> onError(exception)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns the value if Success, null otherwise.
|
|
24
|
+
*/
|
|
25
|
+
fun getOrNull(): T? = when (this) {
|
|
26
|
+
is Success -> value
|
|
27
|
+
is Error -> null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the exception if Error, null otherwise.
|
|
32
|
+
*/
|
|
33
|
+
fun exceptionOrNull(): Exception? = when (this) {
|
|
34
|
+
is Success -> null
|
|
35
|
+
is Error -> exception
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns true if this is a Success.
|
|
40
|
+
*/
|
|
41
|
+
val isSuccess: Boolean get() = this is Success
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns true if this is an Error.
|
|
45
|
+
*/
|
|
46
|
+
val isError: Boolean get() = this is Error
|
|
47
|
+
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
package com.michaelwolz.capacitorcameraview.model
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for a camera session.
|
|
5
|
+
*
|
|
6
|
+
* @property deviceId Specific device ID to use. Takes precedence over position.
|
|
7
|
+
* @property enableBarcodeDetection Whether to enable barcode detection.
|
|
8
|
+
* @property barcodeTypes Optional list of specific barcode format codes to detect.
|
|
9
|
+
* If null, all supported formats are detected.
|
|
10
|
+
* @property position Camera position to use ("front" or "back").
|
|
11
|
+
* @property zoomFactor Initial zoom factor.
|
|
12
|
+
*/
|
|
4
13
|
data class CameraSessionConfiguration(
|
|
5
14
|
val deviceId: String? = null,
|
|
6
15
|
val enableBarcodeDetection: Boolean = false,
|
|
16
|
+
val barcodeTypes: List<Int>? = null,
|
|
7
17
|
val position: String = "back",
|
|
8
18
|
val zoomFactor: Float = 1.0f
|
|
9
19
|
)
|
|
@@ -13,9 +13,53 @@ import androidx.camera.view.PreviewView
|
|
|
13
13
|
import com.getcapacitor.PluginCall
|
|
14
14
|
import com.google.mlkit.vision.barcode.common.Barcode
|
|
15
15
|
import com.michaelwolz.capacitorcameraview.model.CameraSessionConfiguration
|
|
16
|
+
import com.michaelwolz.capacitorcameraview.model.VideoRecordingQuality
|
|
16
17
|
import com.michaelwolz.capacitorcameraview.model.WebBoundingRect
|
|
17
18
|
import java.io.ByteArrayOutputStream
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Memory-efficient Base64 encoding utilities.
|
|
22
|
+
* Uses ThreadLocal ByteArrayOutputStream pool to reduce allocation churn.
|
|
23
|
+
*/
|
|
24
|
+
object StreamingBase64Encoder {
|
|
25
|
+
// Reusable ByteArrayOutputStream to reduce allocation churn (per-thread)
|
|
26
|
+
private val outputStreamPool = object : ThreadLocal<ByteArrayOutputStream>() {
|
|
27
|
+
override fun initialValue(): ByteArrayOutputStream {
|
|
28
|
+
return ByteArrayOutputStream(256 * 1024) // 256KB initial capacity
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Encodes a bitmap to Base64 with memory optimization.
|
|
34
|
+
* Reuses ByteArrayOutputStream to reduce allocations.
|
|
35
|
+
*
|
|
36
|
+
* @param bitmap The bitmap to encode
|
|
37
|
+
* @param quality JPEG compression quality (0-100)
|
|
38
|
+
* @param format Compression format (default JPEG)
|
|
39
|
+
* @return Base64 encoded string
|
|
40
|
+
*/
|
|
41
|
+
fun encodeToBase64(
|
|
42
|
+
bitmap: Bitmap,
|
|
43
|
+
quality: Int,
|
|
44
|
+
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG
|
|
45
|
+
): String {
|
|
46
|
+
val outputStream = outputStreamPool.get()!!
|
|
47
|
+
outputStream.reset() // Clear previous data
|
|
48
|
+
|
|
49
|
+
bitmap.compress(format, quality, outputStream)
|
|
50
|
+
val byteArray = outputStream.toByteArray()
|
|
51
|
+
|
|
52
|
+
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Encodes raw byte array to Base64.
|
|
57
|
+
*/
|
|
58
|
+
fun encodeToBase64(bytes: ByteArray): String {
|
|
59
|
+
return Base64.encodeToString(bytes, Base64.NO_WRAP)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
19
63
|
/** Converts a barcode format code to a readable string. */
|
|
20
64
|
fun getBarcodeFormatString(format: Int): String {
|
|
21
65
|
return when (format) {
|
|
@@ -36,6 +80,43 @@ fun getBarcodeFormatString(format: Int): String {
|
|
|
36
80
|
}
|
|
37
81
|
}
|
|
38
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Converts a string barcode type from JavaScript to ML Kit Barcode format constant.
|
|
85
|
+
*
|
|
86
|
+
* @param stringType The string barcode type from JavaScript.
|
|
87
|
+
* @return The corresponding ML Kit Barcode format constant, or null if not recognized.
|
|
88
|
+
*/
|
|
89
|
+
fun convertToNativeBarcodeFormat(stringType: String): Int? {
|
|
90
|
+
return when (stringType) {
|
|
91
|
+
"qr" -> Barcode.FORMAT_QR_CODE
|
|
92
|
+
"aztec" -> Barcode.FORMAT_AZTEC
|
|
93
|
+
"codabar" -> Barcode.FORMAT_CODABAR
|
|
94
|
+
"code39" -> Barcode.FORMAT_CODE_39
|
|
95
|
+
"code39Mod43" -> Barcode.FORMAT_CODE_39 // ML Kit doesn't distinguish Mod43
|
|
96
|
+
"code93" -> Barcode.FORMAT_CODE_93
|
|
97
|
+
"code128" -> Barcode.FORMAT_CODE_128
|
|
98
|
+
"dataMatrix" -> Barcode.FORMAT_DATA_MATRIX
|
|
99
|
+
"ean8" -> Barcode.FORMAT_EAN_8
|
|
100
|
+
"ean13" -> Barcode.FORMAT_EAN_13
|
|
101
|
+
"interleaved2of5" -> Barcode.FORMAT_ITF
|
|
102
|
+
"itf14" -> Barcode.FORMAT_ITF
|
|
103
|
+
"pdf417" -> Barcode.FORMAT_PDF417
|
|
104
|
+
"upcA" -> Barcode.FORMAT_UPC_A
|
|
105
|
+
"upce" -> Barcode.FORMAT_UPC_E
|
|
106
|
+
else -> null
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Converts an array of string barcode types to ML Kit Barcode format constants.
|
|
112
|
+
*
|
|
113
|
+
* @param stringTypes List of string barcode types from JavaScript.
|
|
114
|
+
* @return List of ML Kit Barcode format constants (invalid types are filtered out).
|
|
115
|
+
*/
|
|
116
|
+
fun convertToNativeBarcodeFormats(stringTypes: List<String>): List<Int> {
|
|
117
|
+
return stringTypes.mapNotNull { convertToNativeBarcodeFormat(it) }.distinct()
|
|
118
|
+
}
|
|
119
|
+
|
|
39
120
|
/**
|
|
40
121
|
* Converts the bounding box of a barcode detection result to a [WebBoundingRect]
|
|
41
122
|
* suitable for use in the web view via regular CSS pixels.
|
|
@@ -84,9 +165,20 @@ fun calculateTopOffset(webView: View): Int {
|
|
|
84
165
|
|
|
85
166
|
/** Maps a Capacitor plugin call to a [CameraSessionConfiguration]. */
|
|
86
167
|
fun sessionConfigFromPluginCall(call: PluginCall): CameraSessionConfiguration {
|
|
168
|
+
// Parse barcode types if provided
|
|
169
|
+
val barcodeTypes: List<Int>? = call.getArray("barcodeTypes")?.let { jsonArray ->
|
|
170
|
+
val stringTypes = mutableListOf<String>()
|
|
171
|
+
for (i in 0 until jsonArray.length()) {
|
|
172
|
+
jsonArray.optString(i)?.let { stringTypes.add(it) }
|
|
173
|
+
}
|
|
174
|
+
val converted = convertToNativeBarcodeFormats(stringTypes)
|
|
175
|
+
converted.ifEmpty { null }
|
|
176
|
+
}
|
|
177
|
+
|
|
87
178
|
return CameraSessionConfiguration(
|
|
88
179
|
deviceId = call.getString("deviceId"),
|
|
89
180
|
enableBarcodeDetection = call.getBoolean("enableBarcodeDetection") ?: false,
|
|
181
|
+
barcodeTypes = barcodeTypes,
|
|
90
182
|
position = call.getString("position") ?: "back",
|
|
91
183
|
zoomFactor = call.getFloat("zoomFactor") ?: 1.0f
|
|
92
184
|
)
|
|
@@ -126,6 +218,7 @@ fun calculateImageRotationBasedOnDisplayRotation(
|
|
|
126
218
|
|
|
127
219
|
/**
|
|
128
220
|
* Converts an ImageProxy to a Base64 encoded string and applies rotation if necessary.
|
|
221
|
+
* Uses StreamingBase64Encoder for memory-efficient encoding.
|
|
129
222
|
*
|
|
130
223
|
* @param image The ImageProxy to convert.
|
|
131
224
|
* @param quality The JPEG compression quality (0-100).
|
|
@@ -152,13 +245,29 @@ fun imageProxyToBase64(image: ImageProxy, quality: Int, rotationDegrees: Int): S
|
|
|
152
245
|
bitmap = rotatedBitmap
|
|
153
246
|
}
|
|
154
247
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
val byteArray = outputStream.toByteArray()
|
|
158
|
-
|
|
159
|
-
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
|
|
248
|
+
// Use streaming encoder for memory efficiency
|
|
249
|
+
return StreamingBase64Encoder.encodeToBase64(bitmap, quality)
|
|
160
250
|
} finally {
|
|
161
251
|
// Ensure bitmap is always recycled
|
|
162
252
|
bitmap.recycle()
|
|
163
253
|
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Parses a string representation of video recording quality into a [VideoRecordingQuality] enum.
|
|
258
|
+
*/
|
|
259
|
+
fun parseVideoRecordingQuality(rawValue: String?): VideoRecordingQuality? {
|
|
260
|
+
if (rawValue == null) {
|
|
261
|
+
return VideoRecordingQuality.HIGHEST
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return when (rawValue) {
|
|
265
|
+
"lowest" -> VideoRecordingQuality.LOWEST
|
|
266
|
+
"sd" -> VideoRecordingQuality.SD
|
|
267
|
+
"hd" -> VideoRecordingQuality.HD
|
|
268
|
+
"fhd" -> VideoRecordingQuality.FHD
|
|
269
|
+
"uhd" -> VideoRecordingQuality.UHD
|
|
270
|
+
"highest" -> VideoRecordingQuality.HIGHEST
|
|
271
|
+
else -> null
|
|
272
|
+
}
|
|
164
273
|
}
|