capacitor-camera-view 2.3.1 → 3.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 +1 -1
- package/Package.swift +1 -1
- package/README.md +341 -48
- package/android/build.gradle +0 -1
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraError.kt +42 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +946 -205
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +87 -43
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +28 -2
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraDevice.kt +6 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +15 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/TorchModeState.kt +15 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/WebBoundingRect.kt +1 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +73 -19
- package/dist/docs.json +487 -30
- package/dist/esm/definitions.d.ts +494 -27
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/utils.d.ts +59 -15
- package/dist/esm/utils.js +79 -38
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/web.d.ts +191 -13
- package/dist/esm/web.js +626 -142
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +713 -180
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +713 -180
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CameraViewPlugin/CameraError.swift +147 -2
- package/ios/Sources/CameraViewPlugin/CameraEvents.swift +41 -19
- package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +29 -1
- package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +78 -23
- package/ios/Sources/CameraViewPlugin/CameraViewManager+DeferredStart.swift +37 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Focus.swift +131 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Lifecycle.swift +156 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +73 -41
- package/ios/Sources/CameraViewPlugin/CameraViewManager+ResolutionSelection.swift +57 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Rotation.swift +195 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +24 -12
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +22 -73
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Zoom.swift +113 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +450 -403
- package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +111 -69
- package/ios/Sources/CameraViewPlugin/Utils.swift +61 -7
- package/package.json +25 -9
|
@@ -35,6 +35,7 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
35
35
|
CAPPluginMethod(name: "flipCamera", returnType: CAPPluginReturnPromise),
|
|
36
36
|
CAPPluginMethod(name: "getZoom", returnType: CAPPluginReturnPromise),
|
|
37
37
|
CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
|
|
38
|
+
CAPPluginMethod(name: "setFocusPoint", returnType: CAPPluginReturnPromise),
|
|
38
39
|
CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
|
|
39
40
|
CAPPluginMethod(name: "getSupportedFlashModes", returnType: CAPPluginReturnPromise),
|
|
40
41
|
CAPPluginMethod(name: "setFlashMode", returnType: CAPPluginReturnPromise),
|
|
@@ -54,25 +55,41 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
54
55
|
public func cameraDidDetectBarcode(_ event: BarcodeDetectedEvent) {
|
|
55
56
|
notifyListeners("barcodeDetected", data: event.toDictionary())
|
|
56
57
|
}
|
|
58
|
+
|
|
59
|
+
public func cameraWasInterrupted(reason: String) {
|
|
60
|
+
notifyListeners("cameraInterrupted", data: ["reason": reason])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public func cameraInterruptionEnded() {
|
|
64
|
+
notifyListeners("cameraResumed", data: [:])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public func cameraRuntimeError(message: String, code: Int?) {
|
|
68
|
+
var data: [String: Any] = ["message": message]
|
|
69
|
+
if let code = code {
|
|
70
|
+
data["code"] = code
|
|
71
|
+
}
|
|
72
|
+
notifyListeners("cameraRuntimeError", data: data)
|
|
73
|
+
}
|
|
57
74
|
|
|
58
75
|
@objc func start(_ call: CAPPluginCall) {
|
|
59
76
|
guard let webView = self.webView else {
|
|
60
|
-
call.reject("Cannot find web view")
|
|
77
|
+
call.reject("Cannot find web view", CameraError.missingWebView.code)
|
|
61
78
|
return
|
|
62
79
|
}
|
|
63
|
-
|
|
80
|
+
|
|
64
81
|
maybeRequestCameraAccess { [weak self] granted in
|
|
65
82
|
guard granted else {
|
|
66
|
-
call.reject("Camera access denied")
|
|
83
|
+
call.reject("Camera access denied", CameraError.permissionDenied.code)
|
|
67
84
|
return
|
|
68
85
|
}
|
|
69
|
-
|
|
86
|
+
|
|
70
87
|
self?.implementation.startSession(
|
|
71
88
|
configuration: sessionConfigFromPluginCall(call),
|
|
72
89
|
webView: webView,
|
|
73
90
|
completion: { (error) in
|
|
74
91
|
if let error = error {
|
|
75
|
-
call.reject("Failed to start camera preview",
|
|
92
|
+
call.reject("Failed to start camera preview", error.cameraErrorCode, error)
|
|
76
93
|
return
|
|
77
94
|
}
|
|
78
95
|
call.resolve()
|
|
@@ -97,22 +114,22 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
97
114
|
let saveToFile = call.getBool("saveToFile", false)
|
|
98
115
|
|
|
99
116
|
guard quality >= 0.0 && quality <= 100.0 else {
|
|
100
|
-
call.reject("Quality must be between 0 and 100")
|
|
117
|
+
call.reject("Quality must be between 0 and 100", CameraError.invalidArgument.code)
|
|
101
118
|
return
|
|
102
119
|
}
|
|
103
|
-
|
|
120
|
+
|
|
104
121
|
// Use optimized Data-based capture to avoid double JPEG encoding
|
|
105
122
|
implementation.capturePhotoData(completion: { [weak self] (data, error) in
|
|
106
123
|
if let error = error {
|
|
107
|
-
call.reject("Failed to capture image",
|
|
124
|
+
call.reject("Failed to capture image", error.cameraErrorCode, error)
|
|
108
125
|
return
|
|
109
126
|
}
|
|
110
|
-
|
|
127
|
+
|
|
111
128
|
guard let originalData = data else {
|
|
112
|
-
call.reject("No image data")
|
|
129
|
+
call.reject("No image data", CameraError.captureOutputMissing.code)
|
|
113
130
|
return
|
|
114
131
|
}
|
|
115
|
-
|
|
132
|
+
|
|
116
133
|
// Determine final image data based on quality setting
|
|
117
134
|
// For quality >= 90%, use original camera JPEG data to avoid re-encoding
|
|
118
135
|
// For lower quality, re-encode to reduce file size
|
|
@@ -121,30 +138,34 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
121
138
|
// Use original JPEG data from camera (avoids quality loss and CPU overhead)
|
|
122
139
|
imageData = originalData
|
|
123
140
|
} else {
|
|
124
|
-
// Re-encode at lower quality for smaller file size
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
// Re-encode at lower quality for smaller file size. Uses ImageIO
|
|
142
|
+
// directly (rather than UIImage.jpegData) so EXIF/GPS metadata
|
|
143
|
+
// from the original camera JPEG survives the re-encode.
|
|
144
|
+
guard let compressedData = reencodeJPEG(data: originalData, compressionQuality: quality / 100.0) else {
|
|
145
|
+
call.reject("Failed to compress image", CameraError.imageCompressionFailed.code)
|
|
128
146
|
return
|
|
129
147
|
}
|
|
130
148
|
imageData = compressedData
|
|
131
149
|
}
|
|
132
|
-
|
|
150
|
+
|
|
133
151
|
if saveToFile {
|
|
134
152
|
// Use TempFileManager for tracked temp files with automatic cleanup
|
|
135
153
|
let tempFileURL = TempFileManager.shared.createTempImageFile()
|
|
136
154
|
do {
|
|
137
155
|
try imageData.write(to: tempFileURL)
|
|
138
|
-
|
|
156
|
+
|
|
139
157
|
// Convert file URL to webView-accessible path using Capacitor bridge
|
|
140
158
|
guard let webPath = self?.bridge?.portablePath(fromLocalURL: tempFileURL)?.absoluteString else {
|
|
141
|
-
call.reject("Failed to create web-accessible path")
|
|
159
|
+
call.reject("Failed to create web-accessible path", CameraError.pathConversionFailed.code)
|
|
142
160
|
return
|
|
143
161
|
}
|
|
144
|
-
|
|
145
|
-
call.resolve([
|
|
162
|
+
|
|
163
|
+
call.resolve([
|
|
164
|
+
"path": tempFileURL.absoluteString,
|
|
165
|
+
"webPath": webPath,
|
|
166
|
+
])
|
|
146
167
|
} catch {
|
|
147
|
-
call.reject("Failed to save image to file",
|
|
168
|
+
call.reject("Failed to save image to file", CameraError.fileWriteFailed.code, error)
|
|
148
169
|
}
|
|
149
170
|
} else {
|
|
150
171
|
// Return as base64
|
|
@@ -160,41 +181,44 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
160
181
|
let saveToFile = call.getBool("saveToFile", false)
|
|
161
182
|
|
|
162
183
|
guard quality >= 0.0 && quality <= 100.0 else {
|
|
163
|
-
call.reject("Quality must be between 0 and 100")
|
|
184
|
+
call.reject("Quality must be between 0 and 100", CameraError.invalidArgument.code)
|
|
164
185
|
return
|
|
165
186
|
}
|
|
166
|
-
|
|
187
|
+
|
|
167
188
|
implementation.captureSnapshot { [weak self] (image, error) in
|
|
168
189
|
if let error = error {
|
|
169
|
-
call.reject("Failed to capture frame",
|
|
190
|
+
call.reject("Failed to capture frame", error.cameraErrorCode, error)
|
|
170
191
|
return
|
|
171
192
|
}
|
|
172
|
-
|
|
193
|
+
|
|
173
194
|
guard let image = image else {
|
|
174
|
-
call.reject("No frame data")
|
|
195
|
+
call.reject("No frame data", CameraError.captureOutputMissing.code)
|
|
175
196
|
return
|
|
176
197
|
}
|
|
177
|
-
|
|
198
|
+
|
|
178
199
|
guard let imageData = image.jpegData(compressionQuality: quality / 100.0) else {
|
|
179
|
-
call.reject("Failed to compress image")
|
|
200
|
+
call.reject("Failed to compress image", CameraError.imageCompressionFailed.code)
|
|
180
201
|
return
|
|
181
202
|
}
|
|
182
|
-
|
|
203
|
+
|
|
183
204
|
if saveToFile {
|
|
184
205
|
// Use TempFileManager for tracked temp files with automatic cleanup
|
|
185
206
|
let tempFileURL = TempFileManager.shared.createTempImageFile()
|
|
186
207
|
do {
|
|
187
208
|
try imageData.write(to: tempFileURL)
|
|
188
|
-
|
|
209
|
+
|
|
189
210
|
// Convert file URL to webView-accessible path using Capacitor bridge
|
|
190
211
|
guard let webPath = self?.bridge?.portablePath(fromLocalURL: tempFileURL)?.absoluteString else {
|
|
191
|
-
call.reject("Failed to create web-accessible path")
|
|
212
|
+
call.reject("Failed to create web-accessible path", CameraError.pathConversionFailed.code)
|
|
192
213
|
return
|
|
193
214
|
}
|
|
194
|
-
|
|
195
|
-
call.resolve([
|
|
215
|
+
|
|
216
|
+
call.resolve([
|
|
217
|
+
"path": tempFileURL.absoluteString,
|
|
218
|
+
"webPath": webPath,
|
|
219
|
+
])
|
|
196
220
|
} catch {
|
|
197
|
-
call.reject("Failed to save sample to file",
|
|
221
|
+
call.reject("Failed to save sample to file", CameraError.fileWriteFailed.code, error)
|
|
198
222
|
}
|
|
199
223
|
} else {
|
|
200
224
|
// Return as base64
|
|
@@ -210,14 +234,14 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
210
234
|
let videoQuality = call.getString("videoQuality") ?? "highest"
|
|
211
235
|
|
|
212
236
|
guard let parsedVideoQuality = VideoRecordingQuality(rawValue: videoQuality) else {
|
|
213
|
-
call.reject("Invalid videoQuality. Use one of: lowest, sd, hd, fhd, uhd, highest")
|
|
237
|
+
call.reject("Invalid videoQuality. Use one of: lowest, sd, hd, fhd, uhd, highest", CameraError.invalidArgument.code)
|
|
214
238
|
return
|
|
215
239
|
}
|
|
216
|
-
|
|
240
|
+
|
|
217
241
|
if enableAudio {
|
|
218
242
|
maybeRequestMicrophoneAccess { [weak self] granted in
|
|
219
243
|
guard granted else {
|
|
220
|
-
call.reject("Microphone access denied")
|
|
244
|
+
call.reject("Microphone access denied", CameraError.permissionDenied.code)
|
|
221
245
|
return
|
|
222
246
|
}
|
|
223
247
|
self?.doStartRecording(call: call, enableAudio: true, videoQuality: parsedVideoQuality)
|
|
@@ -226,7 +250,7 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
226
250
|
doStartRecording(call: call, enableAudio: false, videoQuality: parsedVideoQuality)
|
|
227
251
|
}
|
|
228
252
|
}
|
|
229
|
-
|
|
253
|
+
|
|
230
254
|
private func doStartRecording(
|
|
231
255
|
call: CAPPluginCall,
|
|
232
256
|
enableAudio: Bool,
|
|
@@ -234,34 +258,37 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
234
258
|
) {
|
|
235
259
|
implementation.startRecording(enableAudio: enableAudio, videoQuality: videoQuality) { error in
|
|
236
260
|
if let error = error {
|
|
237
|
-
call.reject("Failed to start recording",
|
|
261
|
+
call.reject("Failed to start recording", error.cameraErrorCode, error)
|
|
238
262
|
return
|
|
239
263
|
}
|
|
240
264
|
call.resolve()
|
|
241
265
|
}
|
|
242
266
|
}
|
|
243
|
-
|
|
267
|
+
|
|
244
268
|
@objc func stopRecording(_ call: CAPPluginCall) {
|
|
245
269
|
implementation.stopRecording { [weak self] (outputURL, error) in
|
|
246
270
|
if let error = error {
|
|
247
|
-
call.reject("Failed to stop recording",
|
|
271
|
+
call.reject("Failed to stop recording", error.cameraErrorCode, error)
|
|
248
272
|
return
|
|
249
273
|
}
|
|
250
|
-
|
|
274
|
+
|
|
251
275
|
guard let outputURL = outputURL else {
|
|
252
|
-
call.reject("No output file URL")
|
|
276
|
+
call.reject("No output file URL", CameraError.captureOutputMissing.code)
|
|
253
277
|
return
|
|
254
278
|
}
|
|
255
|
-
|
|
279
|
+
|
|
256
280
|
guard let webPath = self?.bridge?.portablePath(fromLocalURL: outputURL)?.absoluteString else {
|
|
257
|
-
call.reject("Failed to create web-accessible path")
|
|
281
|
+
call.reject("Failed to create web-accessible path", CameraError.pathConversionFailed.code)
|
|
258
282
|
return
|
|
259
283
|
}
|
|
260
|
-
|
|
261
|
-
call.resolve([
|
|
284
|
+
|
|
285
|
+
call.resolve([
|
|
286
|
+
"path": outputURL.absoluteString,
|
|
287
|
+
"webPath": webPath,
|
|
288
|
+
])
|
|
262
289
|
}
|
|
263
290
|
}
|
|
264
|
-
|
|
291
|
+
|
|
265
292
|
@objc func getAvailableDevices(_ call: CAPPluginCall) {
|
|
266
293
|
let devices = implementation.getAvailableDevices()
|
|
267
294
|
|
|
@@ -281,12 +308,12 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
281
308
|
}
|
|
282
309
|
|
|
283
310
|
@objc func flipCamera(_ call: CAPPluginCall) {
|
|
284
|
-
|
|
285
|
-
|
|
311
|
+
implementation.flipCamera { error in
|
|
312
|
+
if let error = error {
|
|
313
|
+
call.reject("Failed to switch camera", error.cameraErrorCode, error)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
286
316
|
call.resolve()
|
|
287
|
-
} catch {
|
|
288
|
-
call.reject("Failed to switch camera", nil, error)
|
|
289
|
-
return
|
|
290
317
|
}
|
|
291
318
|
}
|
|
292
319
|
|
|
@@ -302,21 +329,36 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
302
329
|
|
|
303
330
|
@objc func setZoom(_ call: CAPPluginCall) {
|
|
304
331
|
guard let level = call.getDouble("level") else {
|
|
305
|
-
call.reject("Zoom level must be provided")
|
|
332
|
+
call.reject("Zoom level must be provided", CameraError.invalidArgument.code)
|
|
306
333
|
return
|
|
307
334
|
}
|
|
308
|
-
|
|
335
|
+
|
|
309
336
|
let ramp = call.getBool("ramp") ?? false
|
|
310
|
-
|
|
337
|
+
|
|
311
338
|
do {
|
|
312
339
|
try implementation.setZoomFactor(level, ramp: ramp)
|
|
313
340
|
call.resolve()
|
|
314
341
|
} catch {
|
|
315
|
-
call.reject("Failed to set zoom level",
|
|
342
|
+
call.reject("Failed to set zoom level", error.cameraErrorCode, error)
|
|
316
343
|
return
|
|
317
344
|
}
|
|
318
345
|
}
|
|
319
346
|
|
|
347
|
+
@objc func setFocusPoint(_ call: CAPPluginCall) {
|
|
348
|
+
guard let pointX = call.getDouble("x"), let pointY = call.getDouble("y") else {
|
|
349
|
+
call.reject("Focus point x and y must be provided", CameraError.invalidArgument.code)
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
implementation.setFocusPoint(x: CGFloat(pointX), y: CGFloat(pointY)) { error in
|
|
354
|
+
if let error = error {
|
|
355
|
+
call.reject("Failed to set focus point", error.cameraErrorCode, error)
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
call.resolve()
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
320
362
|
@objc func getFlashMode(_ call: CAPPluginCall) {
|
|
321
363
|
let flashMode = implementation.getFlashMode()
|
|
322
364
|
|
|
@@ -327,7 +369,7 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
327
369
|
|
|
328
370
|
@objc func getSupportedFlashModes(_ call: CAPPluginCall) {
|
|
329
371
|
let supportedFlashModes = implementation.getSupportedFlashModes()
|
|
330
|
-
let supportedFlashModeStrArr = supportedFlashModes.
|
|
372
|
+
let supportedFlashModeStrArr = supportedFlashModes.compactMap { flashModeToStrMap[$0] }
|
|
331
373
|
|
|
332
374
|
call.resolve([
|
|
333
375
|
"flashModes": supportedFlashModeStrArr
|
|
@@ -336,20 +378,20 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
336
378
|
|
|
337
379
|
@objc func setFlashMode(_ call: CAPPluginCall) {
|
|
338
380
|
guard let mode = call.getString("mode") else {
|
|
339
|
-
call.reject("Flash mode must be provided")
|
|
381
|
+
call.reject("Flash mode must be provided", CameraError.invalidArgument.code)
|
|
340
382
|
return
|
|
341
383
|
}
|
|
342
|
-
|
|
384
|
+
|
|
343
385
|
guard let flashMode = strToFlashModeMap[mode] else {
|
|
344
|
-
call.reject("Invalid flash mode")
|
|
386
|
+
call.reject("Invalid flash mode", CameraError.invalidArgument.code)
|
|
345
387
|
return
|
|
346
388
|
}
|
|
347
|
-
|
|
389
|
+
|
|
348
390
|
do {
|
|
349
391
|
try implementation.setFlashMode(flashMode)
|
|
350
392
|
call.resolve()
|
|
351
393
|
} catch {
|
|
352
|
-
call.reject("Failed to set flash mode",
|
|
394
|
+
call.reject("Failed to set flash mode", error.cameraErrorCode, error)
|
|
353
395
|
}
|
|
354
396
|
}
|
|
355
397
|
|
|
@@ -370,22 +412,22 @@ public class CameraViewPlugin: CAPPlugin, CAPBridgedPlugin, CameraEventDelegate
|
|
|
370
412
|
|
|
371
413
|
@objc func setTorchMode(_ call: CAPPluginCall) {
|
|
372
414
|
guard let enabled = call.getBool("enabled") else {
|
|
373
|
-
call.reject("Enabled parameter is required")
|
|
415
|
+
call.reject("Enabled parameter is required", CameraError.invalidArgument.code)
|
|
374
416
|
return
|
|
375
417
|
}
|
|
376
|
-
|
|
418
|
+
|
|
377
419
|
let level = call.getFloat("level") ?? 1.0
|
|
378
|
-
|
|
420
|
+
|
|
379
421
|
guard level >= 0.0 && level <= 1.0 else {
|
|
380
|
-
call.reject("Level must be between 0.0 and 1.0")
|
|
422
|
+
call.reject("Level must be between 0.0 and 1.0", CameraError.invalidArgument.code)
|
|
381
423
|
return
|
|
382
424
|
}
|
|
383
|
-
|
|
425
|
+
|
|
384
426
|
do {
|
|
385
427
|
try implementation.setTorchMode(enabled: enabled, level: level)
|
|
386
428
|
call.resolve()
|
|
387
429
|
} catch {
|
|
388
|
-
call.reject("Failed to set torch mode",
|
|
430
|
+
call.reject("Failed to set torch mode", error.cameraErrorCode, error)
|
|
389
431
|
}
|
|
390
432
|
}
|
|
391
433
|
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import AVFoundation
|
|
2
|
+
import ImageIO
|
|
3
|
+
import os
|
|
4
|
+
import UniformTypeIdentifiers
|
|
5
|
+
|
|
6
|
+
/// Shared logger for camera plugin diagnostics (visible in Console.app / `log stream`),
|
|
7
|
+
/// used instead of `print()` so failures are visible in production logs.
|
|
8
|
+
internal let cameraViewLogger = Logger(subsystem: "com.michaelwolz.capacitorcameraview", category: "CameraViewManager")
|
|
2
9
|
|
|
3
10
|
// MARK: - Camera Type Conversion
|
|
4
11
|
|
|
@@ -57,25 +64,66 @@ public func convertToNativeCameraTypes(_ stringTypes: [String]) -> [AVCaptureDev
|
|
|
57
64
|
return stringTypes.compactMap { convertToNativeCameraType($0) }
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
// MARK: - Image Re-encoding
|
|
68
|
+
|
|
69
|
+
/// Re-encodes JPEG data at a lower compression quality while preserving the
|
|
70
|
+
/// original image metadata (EXIF, TIFF, GPS, orientation, etc.).
|
|
71
|
+
///
|
|
72
|
+
/// Uses ImageIO directly instead of round-tripping through `UIImage`/
|
|
73
|
+
/// `UIImage.jpegData(compressionQuality:)`, which silently drops all metadata
|
|
74
|
+
/// from the source JPEG.
|
|
75
|
+
///
|
|
76
|
+
/// - Parameters:
|
|
77
|
+
/// - data: The source JPEG data.
|
|
78
|
+
/// - compressionQuality: The lossy compression quality (0.0-1.0).
|
|
79
|
+
/// - Returns: The re-encoded JPEG data, or nil if the source data or
|
|
80
|
+
/// destination could not be created.
|
|
81
|
+
public func reencodeJPEG(data: Data, compressionQuality: CGFloat) -> Data? {
|
|
82
|
+
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
|
|
83
|
+
return nil
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let destinationData = NSMutableData()
|
|
87
|
+
guard let destination = CGImageDestinationCreateWithData(
|
|
88
|
+
destinationData,
|
|
89
|
+
UTType.jpeg.identifier as CFString,
|
|
90
|
+
1,
|
|
91
|
+
nil
|
|
92
|
+
) else {
|
|
93
|
+
return nil
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Start from the source image's own metadata so EXIF/GPS/orientation
|
|
97
|
+
// survive the re-encode, then layer the compression quality on top.
|
|
98
|
+
var properties = (CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any]) ?? [:]
|
|
99
|
+
properties[kCGImageDestinationLossyCompressionQuality] = compressionQuality
|
|
100
|
+
|
|
101
|
+
CGImageDestinationAddImageFromSource(destination, source, 0, properties as CFDictionary)
|
|
102
|
+
|
|
103
|
+
guard CGImageDestinationFinalize(destination) else {
|
|
104
|
+
return nil
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return destinationData as Data
|
|
67
108
|
}
|
|
68
109
|
|
|
69
110
|
// MARK: - Barcode Type Conversion
|
|
70
111
|
|
|
71
112
|
/// All supported barcode types for detection.
|
|
72
113
|
/// These are enabled by default when no specific types are configured.
|
|
114
|
+
///
|
|
115
|
+
/// Note: there is no UPC-A entry. AVFoundation has no UPC-A metadata object
|
|
116
|
+
/// type and reports UPC-A codes as `.ean13` (a UPC-A value is an EAN-13 with a
|
|
117
|
+
/// leading `0`), so on iOS the `upcA` union member is neither requestable nor
|
|
118
|
+
/// emitted — such codes arrive as `ean13`. `.codabar` is available since iOS
|
|
119
|
+
/// 15.4; the deployment target is 16, so no availability guard is needed.
|
|
73
120
|
public let ALL_SUPPORTED_BARCODE_TYPES: [AVMetadataObject.ObjectType] = [
|
|
74
121
|
.qr,
|
|
75
122
|
.code128,
|
|
76
123
|
.code39,
|
|
77
124
|
.code39Mod43,
|
|
78
125
|
.code93,
|
|
126
|
+
.codabar,
|
|
79
127
|
.ean8,
|
|
80
128
|
.ean13,
|
|
81
129
|
.interleaved2of5,
|
|
@@ -101,6 +149,10 @@ public func convertToNativeBarcodeType(_ stringType: String) -> AVMetadataObject
|
|
|
101
149
|
return .code39Mod43
|
|
102
150
|
case "code93":
|
|
103
151
|
return .code93
|
|
152
|
+
case "codabar":
|
|
153
|
+
return .codabar
|
|
154
|
+
// Note: "upcA" is intentionally unmapped. AVFoundation has no UPC-A metadata
|
|
155
|
+
// type and reports UPC-A codes as .ean13, so it cannot be requested on iOS.
|
|
104
156
|
case "ean8":
|
|
105
157
|
return .ean8
|
|
106
158
|
case "ean13":
|
|
@@ -137,6 +189,8 @@ public func convertToStringBarcodeType(_ barcodeType: AVMetadataObject.ObjectTyp
|
|
|
137
189
|
return "code39Mod43"
|
|
138
190
|
case .code93:
|
|
139
191
|
return "code93"
|
|
192
|
+
case .codabar:
|
|
193
|
+
return "codabar"
|
|
140
194
|
case .ean8:
|
|
141
195
|
return "ean8"
|
|
142
196
|
case .ean13:
|
package/package.json
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capacitor-camera-view",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A Capacitor plugin for embedding a live camera feed directly into your app.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/esm/index.d.ts",
|
|
8
8
|
"unpkg": "dist/plugin.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/esm/index.d.ts",
|
|
12
|
+
"import": "./dist/esm/index.js",
|
|
13
|
+
"require": "./dist/plugin.cjs.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
9
18
|
"files": [
|
|
10
19
|
"android/src/main/",
|
|
11
20
|
"android/build.gradle",
|
|
@@ -29,23 +38,29 @@
|
|
|
29
38
|
"plugin",
|
|
30
39
|
"native"
|
|
31
40
|
],
|
|
41
|
+
"packageManager": "pnpm@11.18.0",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=24",
|
|
44
|
+
"pnpm": ">=11"
|
|
45
|
+
},
|
|
32
46
|
"scripts": {
|
|
33
|
-
"verify": "
|
|
47
|
+
"verify": "pnpm run verify:ios && pnpm run verify:android && pnpm run verify:web",
|
|
34
48
|
"verify:ios": "xcodebuild -scheme CapacitorCameraView -destination generic/platform=iOS",
|
|
35
49
|
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
36
|
-
"verify:web": "
|
|
37
|
-
"lint": "
|
|
38
|
-
"lint:ios": "
|
|
39
|
-
"fmt": "
|
|
50
|
+
"verify:web": "pnpm run build",
|
|
51
|
+
"lint": "pnpm run eslint",
|
|
52
|
+
"lint:ios": "pnpm run swiftlint lint",
|
|
53
|
+
"fmt": "pnpm run eslint --fix && pnpm run prettier --write && pnpm run swiftlint --fix --format",
|
|
40
54
|
"eslint": "eslint . --ext ts",
|
|
41
55
|
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
42
56
|
"prettier:check": "prettier --check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
43
57
|
"swiftlint": "node-swiftlint",
|
|
58
|
+
"test": "vitest run",
|
|
44
59
|
"docgen": "docgen --api CameraViewPlugin --output-readme README.md --output-json dist/docs.json",
|
|
45
|
-
"build": "
|
|
60
|
+
"build": "pnpm run clean && pnpm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
46
61
|
"clean": "rimraf ./dist",
|
|
47
62
|
"watch": "tsc --watch",
|
|
48
|
-
"prepublishOnly": "
|
|
63
|
+
"prepublishOnly": "pnpm run build"
|
|
49
64
|
},
|
|
50
65
|
"devDependencies": {
|
|
51
66
|
"@capacitor/android": "^8.3.1",
|
|
@@ -76,7 +91,8 @@
|
|
|
76
91
|
"rollup": "^4.60.2",
|
|
77
92
|
"semantic-release": "^25.0.3",
|
|
78
93
|
"swiftlint": "^2.0.0",
|
|
79
|
-
"typescript": "
|
|
94
|
+
"typescript": "~6.0.3",
|
|
95
|
+
"vitest": "^4.1.10"
|
|
80
96
|
},
|
|
81
97
|
"peerDependencies": {
|
|
82
98
|
"@capacitor/core": ">=8.0.0"
|