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
- public let identifier = "CameraModule"
12
- public let jsName = "CameraModule"
13
- public let pluginMethods: [CAPPluginMethod] = [
14
- CAPPluginMethod(name: "checkPermission", returnType: CAPPluginReturnPromise),
15
- CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise),
16
- CAPPluginMethod(name: "checkGalleryPermission", returnType: CAPPluginReturnPromise),
17
- CAPPluginMethod(name: "requestGalleryPermission", returnType: CAPPluginReturnPromise),
18
- CAPPluginMethod(name: "checkAndRequestGalleryPermission", returnType: CAPPluginReturnPromise),
19
- CAPPluginMethod(name: "pickImageBase64", returnType: CAPPluginReturnPromise),
20
- CAPPluginMethod(name: "startPreview", returnType: CAPPluginReturnPromise),
21
- CAPPluginMethod(name: "stopPreview", returnType: CAPPluginReturnPromise),
22
- CAPPluginMethod(name: "toggleFlash", returnType: CAPPluginReturnPromise),
23
- CAPPluginMethod(name: "hasFlash", returnType: CAPPluginReturnPromise),
24
- CAPPluginMethod(name: "takePhotoBase64", returnType: CAPPluginReturnPromise),
25
- CAPPluginMethod(name: "startBarcodeScan", returnType: CAPPluginReturnPromise),
26
- CAPPluginMethod(name: "stopBarcodeScan", returnType: CAPPluginReturnPromise),
27
- CAPPluginMethod(name: "getLastGalleryImage", returnType: CAPPluginReturnPromise)
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
- DispatchQueue.main.async {
147
- if self.previewView != nil {
148
- call.resolve()
149
- return
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
- guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
153
- call.reject("Camera permission not granted")
154
- return
155
- }
145
+ guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
146
+ call.reject("Camera permission not granted")
147
+ return
148
+ }
156
149
 
157
- let session = AVCaptureSession()
150
+ let session = AVCaptureSession()
158
151
 
159
- guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
160
- let input = try? AVCaptureDeviceInput(device: device) else {
161
- call.reject("Camera not available")
162
- return
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
- if session.canAddInput(input) {
166
- session.addInput(input)
167
- }
158
+ if session.canAddInput(input) {
159
+ session.addInput(input)
160
+ }
168
161
 
169
- if session.canAddOutput(self.photoOutput) {
170
- session.addOutput(self.photoOutput)
171
- }
162
+ if session.canAddOutput(self.photoOutput) {
163
+ session.addOutput(self.photoOutput)
164
+ }
172
165
 
173
- guard let container = self.bridge?.viewController?.view else {
174
- call.reject("No container view")
175
- return
176
- }
166
+ guard let container = self.bridge?.viewController?.view else {
167
+ call.reject("No container view")
168
+ return
169
+ }
177
170
 
178
- guard let webView = self.bridge?.webView as? WKWebView else {
179
- call.reject("No webView")
180
- return
181
- }
171
+ guard let webView = self.bridge?.webView as? WKWebView else {
172
+ call.reject("No webView")
173
+ return
174
+ }
182
175
 
183
- // 1. Hacer el webView transparente
184
- webView.isOpaque = false
185
- webView.backgroundColor = .clear
186
- webView.scrollView.backgroundColor = .clear
187
- webView.scrollView.isOpaque = false
188
-
189
- // 2. Crear previewView
190
- let previewView = UIView()
191
- previewView.translatesAutoresizingMaskIntoConstraints = false
192
- previewView.backgroundColor = .clear // Transparente desde el inicio
193
- previewView.isUserInteractionEnabled = false // No intercepta toques
194
-
195
- // 3. Insertar previewView DETRÁS de todo
196
- container.insertSubview(previewView, at: 0)
197
-
198
- // 4. Configurar constraints para toda la pantalla
199
- NSLayoutConstraint.activate([
200
- previewView.topAnchor.constraint(equalTo: container.topAnchor),
201
- previewView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
202
- previewView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
203
- previewView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
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
- // 5. Asegurar orden de capas
207
- previewView.layer.zPosition = -1 // Detrás de todo
208
- webView.layer.zPosition = 0 // Normal
199
+ // 5. Asegurar orden de capas
200
+ previewView.layer.zPosition = -1 // Detrás de todo
201
+ webView.layer.zPosition = 0 // Normal
209
202
 
210
- // 6. Forzar layout
211
- container.layoutIfNeeded()
203
+ // 6. Forzar layout
204
+ container.layoutIfNeeded()
212
205
 
213
- // 7. Crear previewLayer
214
- let previewLayer = AVCaptureVideoPreviewLayer(session: session)
215
- previewLayer.videoGravity = .resizeAspectFill
216
- previewLayer.frame = previewView.bounds
217
- previewLayer.masksToBounds = true
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
- previewView.layer.insertSublayer(previewLayer, at: 0)
212
+ previewView.layer.insertSublayer(previewLayer, at: 0)
220
213
 
221
- // 8. Guardar referencias
222
- self.previewView = previewView
223
- self.videoPreviewLayer = previewLayer
224
- self.captureSession = session
214
+ // 8. Guardar referencias
215
+ self.previewView = previewView
216
+ self.videoPreviewLayer = previewLayer
217
+ self.captureSession = session
225
218
 
226
- // 9. Configurar sesión
227
- session.sessionPreset = .photo
219
+ // 9. Configurar sesión
220
+ session.sessionPreset = .photo
228
221
 
229
- // 10. Iniciar sesión en background
230
- DispatchQueue.global(qos: .userInitiated).async {
231
- session.startRunning()
222
+ // 10. Iniciar sesión en background
223
+ DispatchQueue.global(qos: .userInitiated).async {
224
+ session.startRunning()
232
225
 
233
- DispatchQueue.main.async {
234
- print("Cámara iniciada correctamente")
235
- print("Preview visible: \(previewView.window != nil ? "SÍ" : "NO")")
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
- call.resolve([
240
- "success": true,
241
- "message": "Preview de cámara iniciado"
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
- // Detener cámara
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
- // Remover preview
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 webView a estado normal (opcional)
268
+ // 🔁 Restaurar WebView
261
269
  if let webView = self.bridge?.webView as? WKWebView {
262
270
  webView.isOpaque = true
263
- webView.backgroundColor = .white // O el color de tu app
271
+ webView.backgroundColor = .white
264
272
  webView.scrollView.backgroundColor = .white
265
273
  webView.scrollView.isOpaque = true
266
274
  }
267
275
 
268
- print("🛑 Preview detenido - webView restaurado")
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
- let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
309
+ let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
304
310
 
305
- guard status == .authorized || status == .limited else {
306
- call.reject("Gallery permission not granted")
307
- return
308
- }
311
+ guard status == .authorized || status == .limited else {
312
+ call.reject("Gallery permission not granted")
313
+ return
314
+ }
309
315
 
310
- let fetchOptions = PHFetchOptions()
311
- fetchOptions.sortDescriptors = [
312
- NSSortDescriptor(key: "creationDate", ascending: false)
313
- ]
314
- fetchOptions.fetchLimit = 1
316
+ let fetchOptions = PHFetchOptions()
317
+ fetchOptions.sortDescriptors = [
318
+ NSSortDescriptor(key: "creationDate", ascending: false)
319
+ ]
320
+ fetchOptions.fetchLimit = 1
315
321
 
316
- let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
322
+ let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
317
323
 
318
- guard let asset = assets.firstObject else {
319
- call.reject("No images found")
320
- return
321
- }
324
+ guard let asset = assets.firstObject else {
325
+ call.reject("No images found")
326
+ return
327
+ }
322
328
 
323
- let imageManager = PHImageManager.default()
324
- let options = PHImageRequestOptions()
325
- options.isSynchronous = true
326
- options.deliveryMode = .highQualityFormat
327
- options.resizeMode = .none
329
+ let imageManager = PHImageManager.default()
330
+ let options = PHImageRequestOptions()
331
+ options.isSynchronous = true
332
+ options.deliveryMode = .highQualityFormat
333
+ options.resizeMode = .none
328
334
 
329
- imageManager.requestImageDataAndOrientation(
330
- for: asset,
331
- options: options
332
- ) { data, _, _, _ in
335
+ imageManager.requestImageDataAndOrientation(
336
+ for: asset,
337
+ options: options
338
+ ) { data, _, _, _ in
333
339
 
334
- guard let imageData = data else {
335
- call.reject("Error fetching last image")
336
- return
337
- }
340
+ guard let imageData = data else {
341
+ call.reject("Error fetching last image")
342
+ return
343
+ }
338
344
 
339
- let base64 = imageData.base64EncodedString()
345
+ let base64 = imageData.base64EncodedString()
340
346
 
341
- var ret = JSObject()
342
- ret["base64"] = base64
343
- call.resolve(ret)
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
- kCVPixelFormatType_32BGRA
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) else { return }
458
+ let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
459
+ else { return }
444
460
 
445
461
  let handler = VNImageRequestHandler(
446
462
  cvPixelBuffer: pixelBuffer,
447
- orientation: CGImagePropertyOrientation.right
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-camera-module",
3
- "version": "0.0.67",
3
+ "version": "0.0.69",
4
4
  "description": "Plugin to request permissiones view camera take phots",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",