capacitor-camera-module 0.0.67 → 0.0.69
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.
|
@@ -5,29 +5,26 @@ import UIKit
|
|
|
5
5
|
import Photos
|
|
6
6
|
|
|
7
7
|
@objc(CameraModule)
|
|
8
|
-
public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
]
|
|
30
|
-
|
|
8
|
+
public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
9
|
+
|
|
10
|
+
public let identifier = "CameraModule"
|
|
11
|
+
public let jsName = "CameraModule"
|
|
12
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
13
|
+
CAPPluginMethod(name: "checkPermission", returnType: CAPPluginReturnPromise),
|
|
14
|
+
CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "checkGalleryPermission", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "requestGalleryPermission", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "checkAndRequestGalleryPermission", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "pickImageBase64", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "startPreview", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "stopPreview", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "toggleFlash", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "hasFlash", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "takePhotoBase64", returnType: CAPPluginReturnPromise),
|
|
24
|
+
CAPPluginMethod(name: "startBarcodeScan", returnType: CAPPluginReturnPromise),
|
|
25
|
+
CAPPluginMethod(name: "stopBarcodeScan", returnType: CAPPluginReturnPromise),
|
|
26
|
+
CAPPluginMethod(name: "getLastGalleryImage", returnType: CAPPluginReturnPromise)
|
|
27
|
+
]
|
|
31
28
|
|
|
32
29
|
private var previewView: UIView?
|
|
33
30
|
private var captureSession: AVCaptureSession?
|
|
@@ -51,13 +48,9 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
51
48
|
|
|
52
49
|
// MARK: - Lifecycle
|
|
53
50
|
public override func load() {
|
|
54
|
-
// Este método ya no fuerza el color negro
|
|
55
|
-
// El webView será configurado dinámicamente en startPreview/stopPreview
|
|
56
51
|
print("📱 CameraModulePlugin cargado")
|
|
57
52
|
}
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
54
|
// MARK: - Camera Permission
|
|
62
55
|
|
|
63
56
|
@objc func checkPermission(_ call: CAPPluginCall) {
|
|
@@ -142,136 +135,149 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
142
135
|
|
|
143
136
|
// MARK: - Camera Preview
|
|
144
137
|
|
|
145
|
-
@objc func startPreview(_ call: CAPPluginCall) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
@objc func startPreview(_ call: CAPPluginCall) {
|
|
139
|
+
DispatchQueue.main.async {
|
|
140
|
+
if self.previewView != nil {
|
|
141
|
+
call.resolve()
|
|
142
|
+
return
|
|
143
|
+
}
|
|
151
144
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
|
|
146
|
+
call.reject("Camera permission not granted")
|
|
147
|
+
return
|
|
148
|
+
}
|
|
156
149
|
|
|
157
|
-
|
|
150
|
+
let session = AVCaptureSession()
|
|
158
151
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
|
|
153
|
+
let input = try? AVCaptureDeviceInput(device: device) else {
|
|
154
|
+
call.reject("Camera not available")
|
|
155
|
+
return
|
|
156
|
+
}
|
|
164
157
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
if session.canAddInput(input) {
|
|
159
|
+
session.addInput(input)
|
|
160
|
+
}
|
|
168
161
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
if session.canAddOutput(self.photoOutput) {
|
|
163
|
+
session.addOutput(self.photoOutput)
|
|
164
|
+
}
|
|
172
165
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
guard let container = self.bridge?.viewController?.view else {
|
|
167
|
+
call.reject("No container view")
|
|
168
|
+
return
|
|
169
|
+
}
|
|
177
170
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
171
|
+
guard let webView = self.bridge?.webView as? WKWebView else {
|
|
172
|
+
call.reject("No webView")
|
|
173
|
+
return
|
|
174
|
+
}
|
|
182
175
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
176
|
+
// 1. Hacer el webView transparente
|
|
177
|
+
webView.isOpaque = false
|
|
178
|
+
webView.backgroundColor = .clear
|
|
179
|
+
webView.scrollView.backgroundColor = .clear
|
|
180
|
+
webView.scrollView.isOpaque = false
|
|
181
|
+
|
|
182
|
+
// 2. Crear previewView
|
|
183
|
+
let previewView = UIView()
|
|
184
|
+
previewView.translatesAutoresizingMaskIntoConstraints = false
|
|
185
|
+
previewView.backgroundColor = .clear // Transparente desde el inicio
|
|
186
|
+
previewView.isUserInteractionEnabled = false // No intercepta toques
|
|
187
|
+
|
|
188
|
+
// 3. Insertar previewView DETRÁS de todo
|
|
189
|
+
container.insertSubview(previewView, at: 0)
|
|
190
|
+
|
|
191
|
+
// 4. Configurar constraints para toda la pantalla
|
|
192
|
+
NSLayoutConstraint.activate([
|
|
193
|
+
previewView.topAnchor.constraint(equalTo: container.topAnchor),
|
|
194
|
+
previewView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
195
|
+
previewView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
|
196
|
+
previewView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
|
|
197
|
+
])
|
|
205
198
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
// 5. Asegurar orden de capas
|
|
200
|
+
previewView.layer.zPosition = -1 // Detrás de todo
|
|
201
|
+
webView.layer.zPosition = 0 // Normal
|
|
209
202
|
|
|
210
|
-
|
|
211
|
-
|
|
203
|
+
// 6. Forzar layout
|
|
204
|
+
container.layoutIfNeeded()
|
|
212
205
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
206
|
+
// 7. Crear previewLayer
|
|
207
|
+
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
|
|
208
|
+
previewLayer.videoGravity = .resizeAspectFill
|
|
209
|
+
previewLayer.frame = previewView.bounds
|
|
210
|
+
previewLayer.masksToBounds = true
|
|
218
211
|
|
|
219
|
-
|
|
212
|
+
previewView.layer.insertSublayer(previewLayer, at: 0)
|
|
220
213
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
214
|
+
// 8. Guardar referencias
|
|
215
|
+
self.previewView = previewView
|
|
216
|
+
self.videoPreviewLayer = previewLayer
|
|
217
|
+
self.captureSession = session
|
|
225
218
|
|
|
226
|
-
|
|
227
|
-
|
|
219
|
+
// 9. Configurar sesión
|
|
220
|
+
session.sessionPreset = .photo
|
|
228
221
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
222
|
+
// 10. Iniciar sesión en background
|
|
223
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
224
|
+
session.startRunning()
|
|
232
225
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
226
|
+
DispatchQueue.main.async {
|
|
227
|
+
print("Cámara iniciada correctamente")
|
|
228
|
+
print("Preview visible: \(previewView.window != nil ? "SÍ" : "NO")")
|
|
229
|
+
}
|
|
236
230
|
}
|
|
237
|
-
}
|
|
238
231
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
232
|
+
call.resolve([
|
|
233
|
+
"success": true,
|
|
234
|
+
"message": "Preview de cámara iniciado"
|
|
235
|
+
])
|
|
236
|
+
}
|
|
243
237
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
238
|
|
|
247
239
|
@objc func stopPreview(_ call: CAPPluginCall) {
|
|
248
240
|
DispatchQueue.main.async {
|
|
249
|
-
|
|
241
|
+
|
|
242
|
+
if self.isScanning {
|
|
243
|
+
self.isScanning = false
|
|
244
|
+
self.scanCall?.keepAlive = false
|
|
245
|
+
self.scanCall = nil
|
|
246
|
+
self.barcodeRequest = nil
|
|
247
|
+
self.lastScannedValue = nil
|
|
248
|
+
self.lastScanTime = nil
|
|
249
|
+
|
|
250
|
+
if let output = self.videoDataOutput,
|
|
251
|
+
let session = self.captureSession {
|
|
252
|
+
session.removeOutput(output)
|
|
253
|
+
}
|
|
254
|
+
self.videoDataOutput = nil
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 🛑 Detener cámara
|
|
250
258
|
self.captureSession?.stopRunning()
|
|
251
259
|
self.captureSession = nil
|
|
252
260
|
|
|
253
|
-
//
|
|
261
|
+
// 🧹 Limpiar preview
|
|
254
262
|
self.videoPreviewLayer?.removeFromSuperlayer()
|
|
255
263
|
self.previewView?.removeFromSuperview()
|
|
256
264
|
self.videoPreviewLayer?.session = nil
|
|
257
265
|
self.videoPreviewLayer = nil
|
|
258
266
|
self.previewView = nil
|
|
259
267
|
|
|
260
|
-
// Restaurar
|
|
268
|
+
// 🔁 Restaurar WebView
|
|
261
269
|
if let webView = self.bridge?.webView as? WKWebView {
|
|
262
270
|
webView.isOpaque = true
|
|
263
|
-
webView.backgroundColor = .white
|
|
271
|
+
webView.backgroundColor = .white
|
|
264
272
|
webView.scrollView.backgroundColor = .white
|
|
265
273
|
webView.scrollView.isOpaque = true
|
|
266
274
|
}
|
|
267
275
|
|
|
268
|
-
print("
|
|
276
|
+
print("Preview y escaneo detenidos correctamente")
|
|
269
277
|
call.resolve()
|
|
270
278
|
}
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
|
|
274
|
-
|
|
275
281
|
// MARK: - Flash
|
|
276
282
|
|
|
277
283
|
@objc func toggleFlash(_ call: CAPPluginCall) {
|
|
@@ -300,49 +306,49 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
300
306
|
|
|
301
307
|
@objc func getLastGalleryImage(_ call: CAPPluginCall) {
|
|
302
308
|
|
|
303
|
-
|
|
309
|
+
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
304
310
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
311
|
+
guard status == .authorized || status == .limited else {
|
|
312
|
+
call.reject("Gallery permission not granted")
|
|
313
|
+
return
|
|
314
|
+
}
|
|
309
315
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
316
|
+
let fetchOptions = PHFetchOptions()
|
|
317
|
+
fetchOptions.sortDescriptors = [
|
|
318
|
+
NSSortDescriptor(key: "creationDate", ascending: false)
|
|
319
|
+
]
|
|
320
|
+
fetchOptions.fetchLimit = 1
|
|
315
321
|
|
|
316
|
-
|
|
322
|
+
let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
317
323
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
guard let asset = assets.firstObject else {
|
|
325
|
+
call.reject("No images found")
|
|
326
|
+
return
|
|
327
|
+
}
|
|
322
328
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
329
|
+
let imageManager = PHImageManager.default()
|
|
330
|
+
let options = PHImageRequestOptions()
|
|
331
|
+
options.isSynchronous = true
|
|
332
|
+
options.deliveryMode = .highQualityFormat
|
|
333
|
+
options.resizeMode = .none
|
|
328
334
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
335
|
+
imageManager.requestImageDataAndOrientation(
|
|
336
|
+
for: asset,
|
|
337
|
+
options: options
|
|
338
|
+
) { data, _, _, _ in
|
|
333
339
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
340
|
+
guard let imageData = data else {
|
|
341
|
+
call.reject("Error fetching last image")
|
|
342
|
+
return
|
|
343
|
+
}
|
|
338
344
|
|
|
339
|
-
|
|
345
|
+
let base64 = imageData.base64EncodedString()
|
|
340
346
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
347
|
+
var ret = JSObject()
|
|
348
|
+
ret["base64"] = base64
|
|
349
|
+
call.resolve(ret)
|
|
345
350
|
}
|
|
351
|
+
}
|
|
346
352
|
|
|
347
353
|
// MARK: - Photo Capture
|
|
348
354
|
|
|
@@ -398,19 +404,26 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
398
404
|
self.lastScanTime = now
|
|
399
405
|
self.isScanning = false
|
|
400
406
|
|
|
407
|
+
if let output = self.videoDataOutput,
|
|
408
|
+
let session = self.captureSession {
|
|
409
|
+
session.removeOutput(output)
|
|
410
|
+
}
|
|
411
|
+
self.videoDataOutput = nil
|
|
412
|
+
|
|
401
413
|
self.scanCall?.resolve([
|
|
402
414
|
"rawValue": value,
|
|
403
415
|
"format": barcode.symbology.rawValue
|
|
404
416
|
])
|
|
405
417
|
self.scanCall?.keepAlive = false
|
|
406
418
|
self.scanCall = nil
|
|
419
|
+
|
|
407
420
|
}
|
|
408
421
|
|
|
409
422
|
if videoDataOutput == nil {
|
|
410
423
|
let output = AVCaptureVideoDataOutput()
|
|
411
424
|
output.videoSettings = [
|
|
412
425
|
kCVPixelBufferPixelFormatTypeKey as String:
|
|
413
|
-
|
|
426
|
+
kCVPixelFormatType_32BGRA
|
|
414
427
|
]
|
|
415
428
|
output.alwaysDiscardsLateVideoFrames = true
|
|
416
429
|
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "barcode.queue"))
|
|
@@ -438,23 +451,51 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
438
451
|
public func captureOutput(_ output: AVCaptureOutput,
|
|
439
452
|
didOutput sampleBuffer: CMSampleBuffer,
|
|
440
453
|
from connection: AVCaptureConnection) {
|
|
454
|
+
|
|
441
455
|
guard isScanning,
|
|
456
|
+
captureSession?.isRunning == true,
|
|
442
457
|
let request = barcodeRequest,
|
|
443
|
-
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
|
|
458
|
+
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
|
|
459
|
+
else { return }
|
|
444
460
|
|
|
445
461
|
let handler = VNImageRequestHandler(
|
|
446
462
|
cvPixelBuffer: pixelBuffer,
|
|
447
|
-
orientation:
|
|
448
|
-
,
|
|
463
|
+
orientation: .right,
|
|
449
464
|
options: [:]
|
|
450
465
|
)
|
|
451
466
|
|
|
452
467
|
try? handler.perform([request])
|
|
453
468
|
}
|
|
469
|
+
|
|
470
|
+
// MARK: - PhotoDelegate Class
|
|
471
|
+
|
|
472
|
+
private class PhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate {
|
|
473
|
+
private let completion: (String) -> Void
|
|
474
|
+
|
|
475
|
+
init(completion: @escaping (String) -> Void) {
|
|
476
|
+
self.completion = completion
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
func photoOutput(_ output: AVCapturePhotoOutput,
|
|
480
|
+
didFinishProcessingPhoto photo: AVCapturePhoto,
|
|
481
|
+
error: Error?) {
|
|
482
|
+
if let error = error {
|
|
483
|
+
print("Error capturando foto: \(error)")
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
guard let data = photo.fileDataRepresentation(),
|
|
488
|
+
let base64 = data.base64EncodedString() as? String else {
|
|
489
|
+
print("No se pudo convertir la imagen a base64")
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
completion(base64)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
454
496
|
}
|
|
455
497
|
|
|
456
498
|
// MARK: - UIImagePicker Delegate
|
|
457
|
-
|
|
458
499
|
extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
459
500
|
|
|
460
501
|
public func imagePickerController(_ picker: UIImagePickerController,
|
|
@@ -485,7 +526,6 @@ extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationContr
|
|
|
485
526
|
}
|
|
486
527
|
|
|
487
528
|
// MARK: - UIImage Resize
|
|
488
|
-
|
|
489
529
|
extension UIImage {
|
|
490
530
|
func resized(maxSize: CGFloat) -> UIImage? {
|
|
491
531
|
let maxSide = max(size.width, size.height)
|
|
@@ -500,6 +540,4 @@ extension UIImage {
|
|
|
500
540
|
UIGraphicsEndImageContext()
|
|
501
541
|
return img
|
|
502
542
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
543
|
+
}
|