capacitor-camera-module 0.0.65 → 0.0.68

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,26 +5,29 @@ import UIKit
5
5
  import Photos
6
6
 
7
7
  @objc(CameraModule)
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
- ]
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
+
28
31
 
29
32
  private var previewView: UIView?
30
33
  private var captureSession: AVCaptureSession?
@@ -33,6 +36,7 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
33
36
  private var photoDelegate: PhotoDelegate?
34
37
 
35
38
  // MARK: - Barcode
39
+
36
40
  private var isScanning = false
37
41
  private var scanCall: CAPPluginCall?
38
42
  private var barcodeRequest: VNDetectBarcodesRequest?
@@ -42,14 +46,20 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
42
46
  private let scanDebounceInterval: TimeInterval = 0.5
43
47
 
44
48
  // MARK: - Gallery
49
+
45
50
  private var galleryCall: CAPPluginCall?
46
51
 
47
52
  // MARK: - Lifecycle
48
53
  public override func load() {
54
+ // Este método ya no fuerza el color negro
55
+ // El webView será configurado dinámicamente en startPreview/stopPreview
49
56
  print("📱 CameraModulePlugin cargado")
50
57
  }
51
58
 
59
+
60
+
52
61
  // MARK: - Camera Permission
62
+
53
63
  @objc func checkPermission(_ call: CAPPluginCall) {
54
64
  let status = AVCaptureDevice.authorizationStatus(for: .video)
55
65
  call.resolve([
@@ -70,8 +80,10 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
70
80
  }
71
81
 
72
82
  // MARK: - Gallery Permission
83
+
73
84
  @objc func checkGalleryPermission(_ call: CAPPluginCall) {
74
85
  let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
86
+
75
87
  switch status {
76
88
  case .authorized, .limited:
77
89
  call.resolve(["granted": true, "status": "granted"])
@@ -104,6 +116,7 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
104
116
  }
105
117
 
106
118
  // MARK: - Pick Image
119
+
107
120
  @objc func pickImageBase64(_ call: CAPPluginCall) {
108
121
  let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
109
122
  guard status == .authorized || status == .limited else {
@@ -128,116 +141,139 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
128
141
  }
129
142
 
130
143
  // MARK: - Camera Preview
131
- @objc func startPreview(_ call: CAPPluginCall) {
132
- DispatchQueue.main.async {
133
- if self.previewView != nil {
134
- call.resolve()
135
- return
136
- }
137
144
 
138
- guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
139
- call.reject("Camera permission not granted")
140
- return
141
- }
145
+ @objc func startPreview(_ call: CAPPluginCall) {
146
+ DispatchQueue.main.async {
147
+ if self.previewView != nil {
148
+ call.resolve()
149
+ return
150
+ }
142
151
 
143
- let session = AVCaptureSession()
152
+ guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
153
+ call.reject("Camera permission not granted")
154
+ return
155
+ }
144
156
 
145
- guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
146
- let input = try? AVCaptureDeviceInput(device: device) else {
147
- call.reject("Camera not available")
148
- return
149
- }
157
+ let session = AVCaptureSession()
150
158
 
151
- if session.canAddInput(input) {
152
- session.addInput(input)
153
- }
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
+ }
154
164
 
155
- if session.canAddOutput(self.photoOutput) {
156
- session.addOutput(self.photoOutput)
157
- }
165
+ if session.canAddInput(input) {
166
+ session.addInput(input)
167
+ }
158
168
 
159
- guard let container = self.bridge?.viewController?.view else {
160
- call.reject("No container view")
161
- return
162
- }
169
+ if session.canAddOutput(self.photoOutput) {
170
+ session.addOutput(self.photoOutput)
171
+ }
163
172
 
164
- guard let webView = self.bridge?.webView as? WKWebView else {
165
- call.reject("No webView")
166
- return
167
- }
173
+ guard let container = self.bridge?.viewController?.view else {
174
+ call.reject("No container view")
175
+ return
176
+ }
168
177
 
169
- // Hacer el webView transparente
170
- webView.isOpaque = false
171
- webView.backgroundColor = .clear
172
- webView.scrollView.backgroundColor = .clear
173
- webView.scrollView.isOpaque = false
174
-
175
- // Crear previewView
176
- let previewView = UIView()
177
- previewView.translatesAutoresizingMaskIntoConstraints = false
178
- previewView.backgroundColor = .clear
179
- previewView.isUserInteractionEnabled = false
180
-
181
- // Insertar previewView DETRÁS de todo
182
- container.insertSubview(previewView, at: 0)
183
-
184
- // Configurar constraints para toda la pantalla
185
- NSLayoutConstraint.activate([
186
- previewView.topAnchor.constraint(equalTo: container.topAnchor),
187
- previewView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
188
- previewView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
189
- previewView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
190
- ])
178
+ guard let webView = self.bridge?.webView as? WKWebView else {
179
+ call.reject("No webView")
180
+ return
181
+ }
191
182
 
192
- // Asegurar orden de capas
193
- previewView.layer.zPosition = -1
194
- webView.layer.zPosition = 0
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
+ ])
195
205
 
196
- // Forzar layout
197
- container.layoutIfNeeded()
206
+ // 5. Asegurar orden de capas
207
+ previewView.layer.zPosition = -1 // Detrás de todo
208
+ webView.layer.zPosition = 0 // Normal
198
209
 
199
- // Crear previewLayer
200
- let previewLayer = AVCaptureVideoPreviewLayer(session: session)
201
- previewLayer.videoGravity = .resizeAspectFill
202
- previewLayer.frame = previewView.bounds
203
- previewLayer.masksToBounds = true
210
+ // 6. Forzar layout
211
+ container.layoutIfNeeded()
204
212
 
205
- previewView.layer.insertSublayer(previewLayer, at: 0)
213
+ // 7. Crear previewLayer
214
+ let previewLayer = AVCaptureVideoPreviewLayer(session: session)
215
+ previewLayer.videoGravity = .resizeAspectFill
216
+ previewLayer.frame = previewView.bounds
217
+ previewLayer.masksToBounds = true
206
218
 
207
- // Guardar referencias
208
- self.previewView = previewView
209
- self.videoPreviewLayer = previewLayer
210
- self.captureSession = session
219
+ previewView.layer.insertSublayer(previewLayer, at: 0)
211
220
 
212
- // Configurar sesión
213
- session.sessionPreset = .photo
221
+ // 8. Guardar referencias
222
+ self.previewView = previewView
223
+ self.videoPreviewLayer = previewLayer
224
+ self.captureSession = session
214
225
 
215
- // Iniciar sesión en background
216
- DispatchQueue.global(qos: .userInitiated).async {
217
- session.startRunning()
218
- }
226
+ // 9. Configurar sesión
227
+ session.sessionPreset = .photo
219
228
 
220
- call.resolve([
221
- "success": true,
222
- "message": "Preview de cámara iniciado"
223
- ])
229
+ // 10. Iniciar sesión en background
230
+ DispatchQueue.global(qos: .userInitiated).async {
231
+ session.startRunning()
232
+
233
+ DispatchQueue.main.async {
234
+ print("Cámara iniciada correctamente")
235
+ print("Preview visible: \(previewView.window != nil ? "SÍ" : "NO")")
236
+ }
224
237
  }
238
+
239
+ call.resolve([
240
+ "success": true,
241
+ "message": "Preview de cámara iniciado"
242
+ ])
225
243
  }
244
+ }
245
+
226
246
 
227
247
  @objc func stopPreview(_ call: CAPPluginCall) {
228
248
  DispatchQueue.main.async {
229
- // Detener cámara
249
+
250
+ if self.isScanning {
251
+ self.isScanning = false
252
+ self.scanCall?.keepAlive = false
253
+ self.scanCall = nil
254
+ self.barcodeRequest = nil
255
+ self.lastScannedValue = nil
256
+ self.lastScanTime = nil
257
+
258
+ if let output = self.videoDataOutput,
259
+ let session = self.captureSession {
260
+ session.removeOutput(output)
261
+ }
262
+ self.videoDataOutput = nil
263
+ }
264
+
265
+ // 🛑 Detener cámara
230
266
  self.captureSession?.stopRunning()
231
267
  self.captureSession = nil
232
268
 
233
- // Remover preview
269
+ // 🧹 Limpiar preview
234
270
  self.videoPreviewLayer?.removeFromSuperlayer()
235
271
  self.previewView?.removeFromSuperview()
236
272
  self.videoPreviewLayer?.session = nil
237
273
  self.videoPreviewLayer = nil
238
274
  self.previewView = nil
239
275
 
240
- // Restaurar webView a estado normal
276
+ // 🔁 Restaurar WebView
241
277
  if let webView = self.bridge?.webView as? WKWebView {
242
278
  webView.isOpaque = true
243
279
  webView.backgroundColor = .white
@@ -245,11 +281,16 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
245
281
  webView.scrollView.isOpaque = true
246
282
  }
247
283
 
284
+ print("Preview y escaneo detenidos correctamente")
248
285
  call.resolve()
249
286
  }
250
287
  }
251
288
 
289
+
290
+
291
+
252
292
  // MARK: - Flash
293
+
253
294
  @objc func toggleFlash(_ call: CAPPluginCall) {
254
295
  guard let enable = call.getBool("enable"),
255
296
  let device = AVCaptureDevice.default(for: .video),
@@ -275,50 +316,53 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
275
316
  }
276
317
 
277
318
  @objc func getLastGalleryImage(_ call: CAPPluginCall) {
278
- let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
279
319
 
280
- guard status == .authorized || status == .limited else {
281
- call.reject("Gallery permission not granted")
282
- return
283
- }
320
+ let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
284
321
 
285
- let fetchOptions = PHFetchOptions()
286
- fetchOptions.sortDescriptors = [
287
- NSSortDescriptor(key: "creationDate", ascending: false)
288
- ]
289
- fetchOptions.fetchLimit = 1
322
+ guard status == .authorized || status == .limited else {
323
+ call.reject("Gallery permission not granted")
324
+ return
325
+ }
290
326
 
291
- let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
327
+ let fetchOptions = PHFetchOptions()
328
+ fetchOptions.sortDescriptors = [
329
+ NSSortDescriptor(key: "creationDate", ascending: false)
330
+ ]
331
+ fetchOptions.fetchLimit = 1
292
332
 
293
- guard let asset = assets.firstObject else {
294
- call.reject("No images found")
295
- return
296
- }
333
+ let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
297
334
 
298
- let imageManager = PHImageManager.default()
299
- let options = PHImageRequestOptions()
300
- options.isSynchronous = true
301
- options.deliveryMode = .highQualityFormat
302
- options.resizeMode = .none
303
-
304
- imageManager.requestImageDataAndOrientation(
305
- for: asset,
306
- options: options
307
- ) { data, _, _, _ in
308
- guard let imageData = data else {
309
- call.reject("Error fetching last image")
335
+ guard let asset = assets.firstObject else {
336
+ call.reject("No images found")
310
337
  return
311
338
  }
312
339
 
313
- let base64 = imageData.base64EncodedString()
340
+ let imageManager = PHImageManager.default()
341
+ let options = PHImageRequestOptions()
342
+ options.isSynchronous = true
343
+ options.deliveryMode = .highQualityFormat
344
+ options.resizeMode = .none
345
+
346
+ imageManager.requestImageDataAndOrientation(
347
+ for: asset,
348
+ options: options
349
+ ) { data, _, _, _ in
314
350
 
315
- var ret = JSObject()
316
- ret["base64"] = base64
317
- call.resolve(ret)
351
+ guard let imageData = data else {
352
+ call.reject("Error fetching last image")
353
+ return
354
+ }
355
+
356
+ let base64 = imageData.base64EncodedString()
357
+
358
+ var ret = JSObject()
359
+ ret["base64"] = base64
360
+ call.resolve(ret)
361
+ }
318
362
  }
319
- }
320
363
 
321
364
  // MARK: - Photo Capture
365
+
322
366
  @objc func takePhotoBase64(_ call: CAPPluginCall) {
323
367
  guard captureSession?.isRunning == true else {
324
368
  call.reject("Camera not started")
@@ -340,6 +384,7 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
340
384
  }
341
385
 
342
386
  // MARK: - Barcode Scan
387
+
343
388
  @objc func startBarcodeScan(_ call: CAPPluginCall) {
344
389
  guard let session = captureSession else {
345
390
  call.reject("Preview not started")
@@ -370,12 +415,19 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
370
415
  self.lastScanTime = now
371
416
  self.isScanning = false
372
417
 
418
+ if let output = self.videoDataOutput,
419
+ let session = self.captureSession {
420
+ session.removeOutput(output)
421
+ }
422
+ self.videoDataOutput = nil
423
+
373
424
  self.scanCall?.resolve([
374
425
  "rawValue": value,
375
426
  "format": barcode.symbology.rawValue
376
427
  ])
377
428
  self.scanCall?.keepAlive = false
378
429
  self.scanCall = nil
430
+
379
431
  }
380
432
 
381
433
  if videoDataOutput == nil {
@@ -410,25 +462,30 @@ public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureVideoData
410
462
  public func captureOutput(_ output: AVCaptureOutput,
411
463
  didOutput sampleBuffer: CMSampleBuffer,
412
464
  from connection: AVCaptureConnection) {
465
+
413
466
  guard isScanning,
467
+ captureSession?.isRunning == true,
414
468
  let request = barcodeRequest,
415
- let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
469
+ let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
470
+ else { return }
416
471
 
417
472
  let handler = VNImageRequestHandler(
418
473
  cvPixelBuffer: pixelBuffer,
419
- orientation: CGImagePropertyOrientation.right,
474
+ orientation: .right,
420
475
  options: [:]
421
476
  )
422
477
 
423
478
  try? handler.perform([request])
424
479
  }
425
- }
480
+
426
481
 
427
482
  // MARK: - UIImagePicker Delegate
483
+
428
484
  extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
429
485
 
430
486
  public func imagePickerController(_ picker: UIImagePickerController,
431
487
  didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
488
+
432
489
  picker.dismiss(animated: true)
433
490
 
434
491
  guard let image = info[.originalImage] as? UIImage,
@@ -454,6 +511,7 @@ extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationContr
454
511
  }
455
512
 
456
513
  // MARK: - UIImage Resize
514
+
457
515
  extension UIImage {
458
516
  func resized(maxSize: CGFloat) -> UIImage? {
459
517
  let maxSide = max(size.width, size.height)
@@ -468,23 +526,6 @@ extension UIImage {
468
526
  UIGraphicsEndImageContext()
469
527
  return img
470
528
  }
471
- }
472
529
 
473
- // MARK: - Photo Delegate (si necesitas esta clase)
474
- class PhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate {
475
- private let completion: (String) -> Void
476
530
 
477
- init(completion: @escaping (String) -> Void) {
478
- self.completion = completion
479
- }
480
-
481
- func photoOutput(_ output: AVCapturePhotoOutput,
482
- didFinishProcessingPhoto photo: AVCapturePhoto,
483
- error: Error?) {
484
- guard let data = photo.fileDataRepresentation(),
485
- let base64 = data.base64EncodedString() else {
486
- return
487
- }
488
- completion(base64)
489
- }
490
- }
531
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-camera-module",
3
- "version": "0.0.65",
3
+ "version": "0.0.68",
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",