capacitor-camera-module 0.0.39 → 0.0.40

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.
@@ -700,7 +700,7 @@ public class CameraModulePlugin extends Plugin {
700
700
  imageCapture,
701
701
  imageAnalysis
702
702
  );
703
-
703
+
704
704
  });
705
705
  }
706
706
 
@@ -754,6 +754,7 @@ public class CameraModulePlugin extends Plugin {
754
754
 
755
755
  }
756
756
 
757
+
757
758
  if (barcodeScanner != null) {
758
759
  barcodeScanner.close();
759
760
  barcodeScanner = null;
@@ -1,23 +1,284 @@
1
- import Foundation
2
1
  import Capacitor
2
+ import AVFoundation
3
+ import Vision
4
+ import UIKit
3
5
 
4
- /**
5
- * Please read the Capacitor iOS Plugin Development Guide
6
- * here: https://capacitorjs.com/docs/plugins/ios
7
- */
8
6
  @objc(CameraModulePlugin)
9
- public class CameraModulePlugin: CAPPlugin, CAPBridgedPlugin {
10
- public let identifier = "CameraModulePlugin"
11
- public let jsName = "CameraModule"
12
- public let pluginMethods: [CAPPluginMethod] = [
13
- CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise)
14
- ]
15
- private let implementation = CameraModule()
16
-
17
- @objc func echo(_ call: CAPPluginCall) {
18
- let value = call.getString("value") ?? ""
7
+ public class CameraModulePlugin: CAPPlugin,
8
+ AVCaptureVideoDataOutputSampleBufferDelegate {
9
+
10
+ private var previewView: UIView?
11
+ private var captureSession: AVCaptureSession?
12
+ private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
13
+
14
+ private var photoOutput = AVCapturePhotoOutput()
15
+ private var isScanning = false
16
+ private var scanCall: CAPPluginCall?
17
+
18
+ private var photoDelegate: PhotoDelegate?
19
+
20
+ private var barcodeRequest: VNDetectBarcodesRequest?
21
+
22
+ private var videoDataOutput: AVCaptureVideoDataOutput?
23
+
24
+ private var lastScannedValue: String?
25
+ private var lastScanTime: Date?
26
+ private let scanDebounceInterval: TimeInterval = 0.5 // medio segundo
27
+
28
+
29
+ // MARK: - Lifecycle
30
+
31
+ public override func load() {
32
+ bridge?.webView?.isOpaque = false
33
+ bridge?.webView?.backgroundColor = .clear
34
+ }
35
+
36
+ // MARK: - Permissions
37
+
38
+ @objc func checkPermission(_ call: CAPPluginCall) {
39
+ let status = AVCaptureDevice.authorizationStatus(for: .video)
40
+
19
41
  call.resolve([
20
- "value": implementation.echo(value)
42
+ "granted": status == .authorized,
43
+ "status": status == .authorized ? "granted" : "prompt"
21
44
  ])
22
45
  }
46
+
47
+ @objc func requestPermission(_ call: CAPPluginCall) {
48
+ AVCaptureDevice.requestAccess(for: .video) { granted in
49
+ DispatchQueue.main.async {
50
+ call.resolve([
51
+ "granted": granted,
52
+ "status": granted ? "granted" : "denied"
53
+ ])
54
+ }
55
+ }
56
+ }
57
+
58
+ // MARK: - Camera Preview
59
+
60
+ @objc func startPreview(_ call: CAPPluginCall) {
61
+ DispatchQueue.main.async {
62
+
63
+ if self.previewView != nil {
64
+ call.resolve()
65
+ return
66
+ }
67
+
68
+ let session = AVCaptureSession()
69
+ session.sessionPreset = .photo
70
+
71
+ guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera,
72
+ for: .video,
73
+ position: .back),
74
+ let input = try? AVCaptureDeviceInput(device: camera)
75
+ else {
76
+ call.reject("Camera not available")
77
+ return
78
+ }
79
+
80
+ session.addInput(input)
81
+ session.addOutput(self.photoOutput)
82
+
83
+ let view = UIView(frame: UIScreen.main.bounds)
84
+ let previewLayer = AVCaptureVideoPreviewLayer(session: session)
85
+ previewLayer.videoGravity = .resizeAspectFill
86
+ previewLayer.frame = view.bounds
87
+
88
+ view.layer.addSublayer(previewLayer)
89
+
90
+ self.bridge?.viewController?.view.insertSubview(view, at: 0)
91
+
92
+ self.previewView = view
93
+ self.videoPreviewLayer = previewLayer
94
+ self.captureSession = session
95
+
96
+ session.startRunning()
97
+ call.resolve()
98
+ }
99
+ }
100
+
101
+ @objc func stopPreview(_ call: CAPPluginCall) {
102
+ DispatchQueue.main.async {
103
+
104
+ self.captureSession?.stopRunning()
105
+ self.captureSession = nil
106
+
107
+ self.videoPreviewLayer?.removeFromSuperlayer()
108
+ self.videoPreviewLayer = nil
109
+
110
+ self.previewView?.removeFromSuperview()
111
+ self.previewView = nil
112
+
113
+ call.resolve()
114
+ }
115
+ }
116
+
117
+ // MARK: - Flash
118
+
119
+ @objc func toggleFlash(_ call: CAPPluginCall) {
120
+ guard let enable = call.getBool("enable") else {
121
+ call.reject("enable parameter is required")
122
+ return
123
+ }
124
+
125
+ guard let device = AVCaptureDevice.default(for: .video),
126
+ device.hasTorch
127
+ else {
128
+ call.reject("Flash not available")
129
+ return
130
+ }
131
+
132
+ do {
133
+ try device.lockForConfiguration()
134
+ device.torchMode = enable ? .on : .off
135
+ device.unlockForConfiguration()
136
+ call.resolve()
137
+ } catch {
138
+ call.reject("Flash error")
139
+ }
140
+ }
141
+
142
+ @objc func hasFlash(_ call: CAPPluginCall) {
143
+ let hasFlash = AVCaptureDevice.default(for: .video)?.hasTorch ?? false
144
+ call.resolve(["hasFlash": hasFlash])
145
+ }
146
+
147
+ // MARK: - Photo Capture
148
+
149
+ @objc func takePhotoBase64(_ call: CAPPluginCall) {
150
+
151
+ guard captureSession?.isRunning == true else {
152
+ call.reject("Camera not started")
153
+ return
154
+ }
155
+
156
+ let settings = AVCapturePhotoSettings()
157
+ settings.flashMode = .off
158
+
159
+ self.photoDelegate = PhotoDelegate { [weak self] base64 in
160
+ call.resolve([
161
+ "base64": base64,
162
+ "mimeType": "image/jpeg"
163
+ ])
164
+
165
+ self?.photoDelegate = nil
166
+ }
167
+
168
+ self.photoOutput.capturePhoto(
169
+ with: settings,
170
+ delegate: self.photoDelegate!
171
+ )
172
+ }
173
+
174
+
175
+ // MARK: - Barcode Scan
176
+
177
+ @objc func startBarcodeScan(_ call: CAPPluginCall) {
178
+
179
+ guard let session = captureSession else {
180
+ call.reject("Preview not started")
181
+ return
182
+ }
183
+
184
+ if isScanning {
185
+ call.reject("Already scanning")
186
+ return
187
+ }
188
+
189
+ isScanning = true
190
+ scanCall = call
191
+ call.keepAlive = true
192
+
193
+ barcodeRequest = VNDetectBarcodesRequest { request, _ in
194
+ guard let results = request.results as? [VNBarcodeObservation],
195
+ let barcode = results.first,
196
+ let value = barcode.payloadStringValue
197
+ else { return }
198
+
199
+ // Debounce
200
+ let now = Date()
201
+ if let last = self.lastScannedValue,
202
+ let lastTime = self.lastScanTime,
203
+ last == value && now.timeIntervalSince(lastTime) < self.scanDebounceInterval {
204
+ return
205
+ }
206
+
207
+ self.lastScannedValue = value
208
+ self.lastScanTime = now
209
+
210
+ self.isScanning = false
211
+ self.scanCall?.resolve([
212
+ "rawValue": value,
213
+ "format": barcode.symbology.rawValue
214
+ ])
215
+ self.scanCall?.keepAlive = false
216
+ self.scanCall = nil
217
+ }
218
+
219
+
220
+ if videoDataOutput == nil {
221
+ let output = AVCaptureVideoDataOutput()
222
+ output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "barcode.queue"))
223
+ session.addOutput(output)
224
+ videoDataOutput = output
225
+ }
226
+ }
227
+
228
+ @objc func stopBarcodeScan(_ call: CAPPluginCall) {
229
+
230
+ isScanning = false
231
+
232
+ if let output = videoDataOutput,
233
+ let session = captureSession {
234
+ session.removeOutput(output)
235
+ videoDataOutput = nil
236
+ }
237
+
238
+ scanCall?.keepAlive = false
239
+ scanCall = nil
240
+
241
+ lastScannedValue = nil
242
+ lastScanTime = nil
243
+
244
+ call.resolve()
245
+ }
246
+
247
+
248
+ public func captureOutput(
249
+ _ output: AVCaptureOutput,
250
+ didOutput sampleBuffer: CMSampleBuffer,
251
+ from connection: AVCaptureConnection
252
+ ) {
253
+ guard isScanning,
254
+ let request = barcodeRequest
255
+ else { return }
256
+
257
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
258
+ return
259
+ }
260
+
261
+ let handler = VNImageRequestHandler(
262
+ cvPixelBuffer: pixelBuffer,
263
+ orientation: cgOrientation(from: UIDevice.current.orientation),
264
+ options: [:]
265
+ )
266
+
267
+ do {
268
+ try handler.perform([request])
269
+ } catch {
270
+
271
+ }
272
+ }
273
+
274
+ func cgOrientation(from deviceOrientation: UIDeviceOrientation) -> CGImagePropertyOrientation {
275
+ switch deviceOrientation {
276
+ case .portrait: return .right
277
+ case .portraitUpsideDown: return .left
278
+ case .landscapeLeft: return .up
279
+ case .landscapeRight: return .down
280
+ default: return .right
281
+ }
282
+ }
283
+
23
284
  }
@@ -0,0 +1,25 @@
1
+ import AVFoundation
2
+ import UIKit
3
+
4
+ class PhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate {
5
+
6
+
7
+ private let callback: (String) -> Void
8
+
9
+ init(callback: @escaping (String) -> Void) {
10
+ self.callback = callback
11
+ }
12
+
13
+ func photoOutput(_ output: AVCapturePhotoOutput,
14
+ didFinishProcessingPhoto photo: AVCapturePhoto,
15
+ error: Error?) {
16
+
17
+ guard let data = photo.fileDataRepresentation(),
18
+ let image = UIImage(data: data),
19
+ let jpeg = image.jpegData(compressionQuality: 0.8)
20
+ else { return }
21
+
22
+ let base64 = jpeg.base64EncodedString()
23
+ callback(base64)
24
+ }
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-camera-module",
3
- "version": "0.0.39",
3
+ "version": "0.0.40",
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",