capacitor-camera-view 1.0.0
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/CapacitorCameraView.podspec +17 -0
- package/LICENSE +201 -0
- package/Package.swift +28 -0
- package/README.md +654 -0
- package/android/build.gradle +79 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +555 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +227 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +11 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraDevice.kt +14 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +10 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/WebBoundingRect.kt +16 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/ZoomFactors.kt +14 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +86 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +968 -0
- package/dist/esm/definitions.d.ts +378 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/utils.d.ts +45 -0
- package/dist/esm/utils.js +108 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/web.d.ts +108 -0
- package/dist/esm/web.js +406 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +530 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +533 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CameraViewPlugin/CameraError.swift +39 -0
- package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +32 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +91 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +52 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +78 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +633 -0
- package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +295 -0
- package/ios/Sources/CameraViewPlugin/Utils.swift +56 -0
- package/ios/Tests/CameraViewPluginTests/CameraViewPluginTests.swift +15 -0
- package/package.json +94 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import com.getcapacitor.JSArray
|
|
5
|
+
import com.getcapacitor.JSObject
|
|
6
|
+
import com.getcapacitor.PermissionState
|
|
7
|
+
import com.getcapacitor.Plugin
|
|
8
|
+
import com.getcapacitor.PluginCall
|
|
9
|
+
import com.getcapacitor.PluginMethod
|
|
10
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
11
|
+
import com.getcapacitor.annotation.Permission
|
|
12
|
+
import com.getcapacitor.annotation.PermissionCallback
|
|
13
|
+
import com.michaelwolz.capacitorcameraview.model.BarcodeDetectionResult
|
|
14
|
+
|
|
15
|
+
@CapacitorPlugin(
|
|
16
|
+
name = "CameraView",
|
|
17
|
+
permissions = [Permission(strings = [Manifest.permission.CAMERA], alias = "camera")]
|
|
18
|
+
)
|
|
19
|
+
class CameraViewPlugin : Plugin() {
|
|
20
|
+
private val implementation by lazy {
|
|
21
|
+
CameraView(this)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@PluginMethod
|
|
25
|
+
fun start(call: PluginCall) {
|
|
26
|
+
if (getPermissionState("camera") == PermissionState.GRANTED) {
|
|
27
|
+
startCamera(call)
|
|
28
|
+
} else {
|
|
29
|
+
requestPermissionForAlias("camera", call, "cameraPermsCallback")
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@PermissionCallback
|
|
34
|
+
private fun cameraPermsCallback(call: PluginCall) {
|
|
35
|
+
if (getPermissionState("camera") == PermissionState.GRANTED) {
|
|
36
|
+
startCamera(call)
|
|
37
|
+
} else {
|
|
38
|
+
call.reject("Permission is required to take a picture")
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private fun startCamera(call: PluginCall) {
|
|
43
|
+
implementation.startSession(
|
|
44
|
+
config = sessionConfigFromPluginCall(call),
|
|
45
|
+
callback = { error ->
|
|
46
|
+
if (error != null) {
|
|
47
|
+
call.reject("Failed to start camera preview: ${error.localizedMessage}", error)
|
|
48
|
+
} else {
|
|
49
|
+
call.resolve()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@PluginMethod
|
|
56
|
+
fun stop(call: PluginCall) {
|
|
57
|
+
implementation.stopSession { error ->
|
|
58
|
+
if (error != null) {
|
|
59
|
+
call.reject("Failed to stop camera preview: ${error.localizedMessage}", error)
|
|
60
|
+
} else {
|
|
61
|
+
call.resolve()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@PluginMethod
|
|
67
|
+
fun isRunning(call: PluginCall) {
|
|
68
|
+
val result = JSObject().apply {
|
|
69
|
+
put("isRunning", implementation.isRunning())
|
|
70
|
+
}
|
|
71
|
+
call.resolve(result)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@PluginMethod
|
|
75
|
+
fun capture(call: PluginCall) {
|
|
76
|
+
val quality = call.getInt("quality", 90)
|
|
77
|
+
|
|
78
|
+
if (quality !in 0..100) {
|
|
79
|
+
call.reject("Quality must be between 0 and 100")
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
implementation.capturePhoto(quality) { photo, error ->
|
|
84
|
+
when {
|
|
85
|
+
error != null -> call.reject("Failed to capture image: ${error.message}", error)
|
|
86
|
+
photo == null -> call.reject("No image data")
|
|
87
|
+
else -> call.resolve(JSObject().apply { put("photo", photo) })
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@PluginMethod
|
|
93
|
+
fun captureSample(call: PluginCall) {
|
|
94
|
+
val quality = call.getInt("quality", 90)
|
|
95
|
+
|
|
96
|
+
if (quality !in 0..100) {
|
|
97
|
+
call.reject("Quality must be between 0 and 100")
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
implementation.captureSampleFromPreview(quality) { photo, error ->
|
|
102
|
+
when {
|
|
103
|
+
error != null -> call.reject("Failed to capture frame: ${error.message}", error)
|
|
104
|
+
photo == null -> call.reject("No frame data")
|
|
105
|
+
else -> call.resolve(JSObject().apply { put("photo", photo) })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@PluginMethod
|
|
111
|
+
fun getAvailableDevices(call: PluginCall) {
|
|
112
|
+
val devices = implementation.getAvailableDevices()
|
|
113
|
+
val devicesArray = JSArray().apply {
|
|
114
|
+
devices.forEach { device ->
|
|
115
|
+
put(JSObject().apply {
|
|
116
|
+
put("id", device.id)
|
|
117
|
+
put("name", device.name)
|
|
118
|
+
put("position", device.position)
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
call.resolve(JSObject().apply { put("devices", devicesArray) })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@PluginMethod
|
|
127
|
+
fun flipCamera(call: PluginCall) {
|
|
128
|
+
implementation.flipCamera { error ->
|
|
129
|
+
if (error != null) {
|
|
130
|
+
call.reject("Failed to flip camera: ${error.localizedMessage}", error)
|
|
131
|
+
} else {
|
|
132
|
+
call.resolve()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@PluginMethod
|
|
138
|
+
fun getZoom(call: PluginCall) {
|
|
139
|
+
implementation.getSupportedZoomFactors { zoomFactors ->
|
|
140
|
+
call.resolve(JSObject().apply {
|
|
141
|
+
put("min", zoomFactors.min)
|
|
142
|
+
put("max", zoomFactors.max)
|
|
143
|
+
put("current", zoomFactors.current)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@PluginMethod
|
|
149
|
+
fun setZoom(call: PluginCall) {
|
|
150
|
+
val level = call.getFloat("level")
|
|
151
|
+
if (level == null) {
|
|
152
|
+
call.reject("Zoom level must be provided")
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
implementation.setZoomFactor(level) { error ->
|
|
157
|
+
if (error != null) {
|
|
158
|
+
call.reject(error.localizedMessage)
|
|
159
|
+
} else {
|
|
160
|
+
call.resolve()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@PluginMethod
|
|
166
|
+
fun getFlashMode(call: PluginCall) {
|
|
167
|
+
call.resolve(JSObject().apply {
|
|
168
|
+
put("flashMode", implementation.getFlashMode())
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@PluginMethod
|
|
173
|
+
fun getSupportedFlashModes(call: PluginCall) {
|
|
174
|
+
implementation.getSupportedFlashModes { supportedFlashModes ->
|
|
175
|
+
val modesArray = JSArray().apply {
|
|
176
|
+
supportedFlashModes.forEach { put(it) }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
call.resolve(JSObject().apply { put("flashModes", modesArray) })
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@PluginMethod
|
|
184
|
+
fun setFlashMode(call: PluginCall) {
|
|
185
|
+
val mode = call.getString("mode")
|
|
186
|
+
if (mode == null) {
|
|
187
|
+
call.reject("Flash mode must be provided")
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
val validModes = listOf("off", "on", "auto")
|
|
192
|
+
if (!validModes.contains(mode)) {
|
|
193
|
+
call.reject("Invalid flash mode. Must be one of: ${validModes.joinToString(", ")}")
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
implementation.setFlashMode(mode)
|
|
199
|
+
call.resolve()
|
|
200
|
+
} catch (e: Exception) {
|
|
201
|
+
call.reject("Failed to set flash mode: ${e.localizedMessage}", e)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Called by the CameraView when a barcode is detected.
|
|
207
|
+
*/
|
|
208
|
+
fun notifyBarcodeDetected(result: BarcodeDetectionResult) {
|
|
209
|
+
val jsObject = JSObject().apply {
|
|
210
|
+
put("value", result.value)
|
|
211
|
+
put("type", result.type)
|
|
212
|
+
put("boundingRect", JSObject().apply {
|
|
213
|
+
put("x", result.boundingRect.x)
|
|
214
|
+
put("y", result.boundingRect.y)
|
|
215
|
+
put("width", result.boundingRect.width)
|
|
216
|
+
put("height", result.boundingRect.height)
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
notifyListeners("barcodeDetected", jsObject)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
override fun handleOnDestroy() {
|
|
224
|
+
implementation.cleanup()
|
|
225
|
+
super.handleOnDestroy()
|
|
226
|
+
}
|
|
227
|
+
}
|
package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview.model
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Barcode detection result containing the value, format type, and normalized bounding rectangle.
|
|
5
|
+
*/
|
|
6
|
+
data class BarcodeDetectionResult(
|
|
7
|
+
val value: String,
|
|
8
|
+
val displayValue: String,
|
|
9
|
+
val type: String,
|
|
10
|
+
val boundingRect: WebBoundingRect
|
|
11
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview.model
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a camera device available on the Android device.
|
|
5
|
+
*
|
|
6
|
+
* @property id Unique identifier for the camera
|
|
7
|
+
* @property name Human-readable name of the camera
|
|
8
|
+
* @property position Position of the camera ("front" or "back")
|
|
9
|
+
*/
|
|
10
|
+
data class CameraDevice(
|
|
11
|
+
val id: String,
|
|
12
|
+
val name: String,
|
|
13
|
+
val position: String
|
|
14
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview.model
|
|
2
|
+
|
|
3
|
+
/** Configuration for a camera session. */
|
|
4
|
+
data class CameraSessionConfiguration(
|
|
5
|
+
val deviceId: String? = null,
|
|
6
|
+
val enableBarcodeDetection: Boolean = false,
|
|
7
|
+
val position: String = "back",
|
|
8
|
+
val zoomFactor: Float = 1.0f
|
|
9
|
+
)
|
|
10
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview.model
|
|
2
|
+
|
|
3
|
+
/** Normalized rectangle for barcode bounds. */
|
|
4
|
+
data class WebBoundingRect(
|
|
5
|
+
/** Top left x coordinate of the rectangle. */
|
|
6
|
+
val x: Float,
|
|
7
|
+
|
|
8
|
+
/** Top left y coordinate of the rectangle. */
|
|
9
|
+
val y: Float,
|
|
10
|
+
|
|
11
|
+
/** Width of the rectangle. */
|
|
12
|
+
val width: Float,
|
|
13
|
+
|
|
14
|
+
/** Height of the rectangle. */
|
|
15
|
+
val height: Float
|
|
16
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview.model
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents supported zoom factors for the camera.
|
|
5
|
+
*
|
|
6
|
+
* @property min Minimum zoom level (typically 1.0)
|
|
7
|
+
* @property max Maximum zoom level supported by the device
|
|
8
|
+
* @property current Current zoom level in use
|
|
9
|
+
*/
|
|
10
|
+
data class ZoomFactors(
|
|
11
|
+
val min: Float,
|
|
12
|
+
val max: Float,
|
|
13
|
+
val current: Float
|
|
14
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
package com.michaelwolz.capacitorcameraview
|
|
2
|
+
|
|
3
|
+
import android.graphics.Rect
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.view.ViewGroup.MarginLayoutParams
|
|
6
|
+
import androidx.camera.view.PreviewView
|
|
7
|
+
import com.getcapacitor.PluginCall
|
|
8
|
+
import com.google.mlkit.vision.barcode.common.Barcode
|
|
9
|
+
import com.michaelwolz.capacitorcameraview.model.CameraSessionConfiguration
|
|
10
|
+
import com.michaelwolz.capacitorcameraview.model.WebBoundingRect
|
|
11
|
+
|
|
12
|
+
/** Converts a barcode format code to a readable string. */
|
|
13
|
+
fun getBarcodeFormatString(format: Int): String {
|
|
14
|
+
return when (format) {
|
|
15
|
+
Barcode.FORMAT_QR_CODE -> "qr"
|
|
16
|
+
Barcode.FORMAT_AZTEC -> "aztec"
|
|
17
|
+
Barcode.FORMAT_CODABAR -> "codabar"
|
|
18
|
+
Barcode.FORMAT_CODE_39 -> "code39"
|
|
19
|
+
Barcode.FORMAT_CODE_93 -> "code93"
|
|
20
|
+
Barcode.FORMAT_CODE_128 -> "code128"
|
|
21
|
+
Barcode.FORMAT_DATA_MATRIX -> "dataMatrix"
|
|
22
|
+
Barcode.FORMAT_EAN_8 -> "ean8"
|
|
23
|
+
Barcode.FORMAT_EAN_13 -> "ean13"
|
|
24
|
+
Barcode.FORMAT_ITF -> "itf"
|
|
25
|
+
Barcode.FORMAT_PDF417 -> "pdf417"
|
|
26
|
+
Barcode.FORMAT_UPC_A -> "upcA"
|
|
27
|
+
Barcode.FORMAT_UPC_E -> "upcE"
|
|
28
|
+
else -> "unknown"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Converts the bounding box of a barcode detection result to a [WebBoundingRect]
|
|
34
|
+
* suitable for use in the web view via regular CSS pixels.
|
|
35
|
+
*
|
|
36
|
+
* @param previewView The [PreviewView] used for the camera preview.
|
|
37
|
+
* @param boundingBox The bounding box of the barcode detection result.
|
|
38
|
+
* @param topOffset The top offset to be subtracted from the bounding box's top coordinate.
|
|
39
|
+
*/
|
|
40
|
+
fun boundingBoxToWebBoundingRect(
|
|
41
|
+
previewView: PreviewView,
|
|
42
|
+
boundingBox: Rect?,
|
|
43
|
+
topOffset: Int = 0
|
|
44
|
+
): WebBoundingRect {
|
|
45
|
+
if (boundingBox == null) {
|
|
46
|
+
return WebBoundingRect(0f, 0f, 0f, 0f)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
val devicePixelRatio = previewView.context.resources.displayMetrics.density
|
|
50
|
+
|
|
51
|
+
return WebBoundingRect(
|
|
52
|
+
x = boundingBox.left.toFloat() / devicePixelRatio,
|
|
53
|
+
y = (boundingBox.top.toFloat() - topOffset) / devicePixelRatio,
|
|
54
|
+
width = boundingBox.width().toFloat() / devicePixelRatio,
|
|
55
|
+
height = boundingBox.height().toFloat() / devicePixelRatio
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Helper method for calculating the top margin of the capacitor web view for correctly
|
|
61
|
+
* positioning the barcode detection rectangle due to weird android edge-to-edge behavior
|
|
62
|
+
* with web views and capacitors hack around this:
|
|
63
|
+
* https://github.com/ionic-team/capacitor/pull/7871
|
|
64
|
+
*
|
|
65
|
+
* Not subtracting the margins will lead to the barcode detection rectangle being
|
|
66
|
+
* positioned incorrectly too low on the screen.
|
|
67
|
+
*/
|
|
68
|
+
fun calculateTopOffset(webView: View): Int {
|
|
69
|
+
val layoutParams = webView.layoutParams
|
|
70
|
+
|
|
71
|
+
if (layoutParams is MarginLayoutParams) {
|
|
72
|
+
return layoutParams.topMargin
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Maps a Capacitor plugin call to a [CameraSessionConfiguration]. */
|
|
79
|
+
fun sessionConfigFromPluginCall(call: PluginCall): CameraSessionConfiguration {
|
|
80
|
+
return CameraSessionConfiguration(
|
|
81
|
+
deviceId = call.getString("deviceId"),
|
|
82
|
+
enableBarcodeDetection = call.getBoolean("enableBarcodeDetection") ?: false,
|
|
83
|
+
position = call.getString("position") ?: "back",
|
|
84
|
+
zoomFactor = call.getFloat("zoomFactor") ?: 1.0f
|
|
85
|
+
)
|
|
86
|
+
}
|
|
File without changes
|