capacitor-camera-module 0.0.64 → 0.0.65
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?
|
|
@@ -36,7 +33,6 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
36
33
|
private var photoDelegate: PhotoDelegate?
|
|
37
34
|
|
|
38
35
|
// MARK: - Barcode
|
|
39
|
-
|
|
40
36
|
private var isScanning = false
|
|
41
37
|
private var scanCall: CAPPluginCall?
|
|
42
38
|
private var barcodeRequest: VNDetectBarcodesRequest?
|
|
@@ -46,20 +42,14 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
46
42
|
private let scanDebounceInterval: TimeInterval = 0.5
|
|
47
43
|
|
|
48
44
|
// MARK: - Gallery
|
|
49
|
-
|
|
50
45
|
private var galleryCall: CAPPluginCall?
|
|
51
46
|
|
|
52
47
|
// MARK: - Lifecycle
|
|
53
48
|
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
49
|
print("📱 CameraModulePlugin cargado")
|
|
57
50
|
}
|
|
58
51
|
|
|
59
|
-
|
|
60
|
-
|
|
61
52
|
// MARK: - Camera Permission
|
|
62
|
-
|
|
63
53
|
@objc func checkPermission(_ call: CAPPluginCall) {
|
|
64
54
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
65
55
|
call.resolve([
|
|
@@ -80,10 +70,8 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
80
70
|
}
|
|
81
71
|
|
|
82
72
|
// MARK: - Gallery Permission
|
|
83
|
-
|
|
84
73
|
@objc func checkGalleryPermission(_ call: CAPPluginCall) {
|
|
85
74
|
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
86
|
-
|
|
87
75
|
switch status {
|
|
88
76
|
case .authorized, .limited:
|
|
89
77
|
call.resolve(["granted": true, "status": "granted"])
|
|
@@ -116,7 +104,6 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
116
104
|
}
|
|
117
105
|
|
|
118
106
|
// MARK: - Pick Image
|
|
119
|
-
|
|
120
107
|
@objc func pickImageBase64(_ call: CAPPluginCall) {
|
|
121
108
|
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
122
109
|
guard status == .authorized || status == .limited else {
|
|
@@ -141,131 +128,101 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
141
128
|
}
|
|
142
129
|
|
|
143
130
|
// MARK: - Camera Preview
|
|
131
|
+
@objc func startPreview(_ call: CAPPluginCall) {
|
|
132
|
+
DispatchQueue.main.async {
|
|
133
|
+
if self.previewView != nil {
|
|
134
|
+
call.resolve()
|
|
135
|
+
return
|
|
136
|
+
}
|
|
144
137
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
|
|
153
|
-
call.reject("Camera permission not granted")
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let session = AVCaptureSession()
|
|
158
|
-
|
|
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
|
-
}
|
|
164
|
-
|
|
165
|
-
if session.canAddInput(input) {
|
|
166
|
-
session.addInput(input)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if session.canAddOutput(self.photoOutput) {
|
|
170
|
-
session.addOutput(self.photoOutput)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
guard let container = self.bridge?.viewController?.view else {
|
|
174
|
-
call.reject("No container view")
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
guard let webView = self.bridge?.webView as? WKWebView else {
|
|
179
|
-
call.reject("No webView")
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// 1. Hacer el webView transparente ANTES de crear el preview
|
|
184
|
-
webView.isOpaque = false
|
|
185
|
-
webView.backgroundColor = .clear
|
|
186
|
-
webView.scrollView.backgroundColor = .clear
|
|
187
|
-
|
|
188
|
-
// Si necesitas mantener scroll pero transparente:
|
|
189
|
-
webView.scrollView.isOpaque = false
|
|
190
|
-
|
|
191
|
-
// 2. Crear previewView
|
|
192
|
-
let previewView = UIView()
|
|
193
|
-
previewView.translatesAutoresizingMaskIntoConstraints = false
|
|
194
|
-
previewView.backgroundColor = .black // Temporal para debug
|
|
195
|
-
previewView.isUserInteractionEnabled = false // Importante: no intercepta toques
|
|
196
|
-
|
|
197
|
-
// DEBUG: Borde para ver los límites
|
|
198
|
-
previewView.layer.borderWidth = 2
|
|
199
|
-
previewView.layer.borderColor = UIColor.green.cgColor
|
|
138
|
+
guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized else {
|
|
139
|
+
call.reject("Camera permission not granted")
|
|
140
|
+
return
|
|
141
|
+
}
|
|
200
142
|
|
|
201
|
-
|
|
202
|
-
print("🔍 DEBUG: WebView frame: \(webView.frame)")
|
|
143
|
+
let session = AVCaptureSession()
|
|
203
144
|
|
|
204
|
-
|
|
205
|
-
|
|
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
|
+
}
|
|
206
150
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
previewView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
211
|
-
previewView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
|
212
|
-
previewView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
|
|
213
|
-
])
|
|
151
|
+
if session.canAddInput(input) {
|
|
152
|
+
session.addInput(input)
|
|
153
|
+
}
|
|
214
154
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
155
|
+
if session.canAddOutput(self.photoOutput) {
|
|
156
|
+
session.addOutput(self.photoOutput)
|
|
157
|
+
}
|
|
218
158
|
|
|
219
|
-
|
|
220
|
-
|
|
159
|
+
guard let container = self.bridge?.viewController?.view else {
|
|
160
|
+
call.reject("No container view")
|
|
161
|
+
return
|
|
162
|
+
}
|
|
221
163
|
|
|
222
|
-
|
|
164
|
+
guard let webView = self.bridge?.webView as? WKWebView else {
|
|
165
|
+
call.reject("No webView")
|
|
166
|
+
return
|
|
167
|
+
}
|
|
223
168
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
])
|
|
229
191
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
192
|
+
// Asegurar orden de capas
|
|
193
|
+
previewView.layer.zPosition = -1
|
|
194
|
+
webView.layer.zPosition = 0
|
|
233
195
|
|
|
234
|
-
|
|
196
|
+
// Forzar layout
|
|
197
|
+
container.layoutIfNeeded()
|
|
235
198
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
199
|
+
// Crear previewLayer
|
|
200
|
+
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
|
|
201
|
+
previewLayer.videoGravity = .resizeAspectFill
|
|
202
|
+
previewLayer.frame = previewView.bounds
|
|
203
|
+
previewLayer.masksToBounds = true
|
|
240
204
|
|
|
241
|
-
|
|
242
|
-
session.sessionPreset = .photo
|
|
205
|
+
previewView.layer.insertSublayer(previewLayer, at: 0)
|
|
243
206
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
207
|
+
// Guardar referencias
|
|
208
|
+
self.previewView = previewView
|
|
209
|
+
self.videoPreviewLayer = previewLayer
|
|
210
|
+
self.captureSession = session
|
|
247
211
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
print("📍 Preview visible: \(previewView.window != nil ? "SÍ" : "NO")")
|
|
251
|
-
print("📍 WebView transparente: \(webView.isOpaque ? "NO" : "SÍ")")
|
|
212
|
+
// Configurar sesión
|
|
213
|
+
session.sessionPreset = .photo
|
|
252
214
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
previewLayer.borderWidth = 0
|
|
257
|
-
previewView.backgroundColor = .clear // Ahora sí transparente
|
|
258
|
-
}
|
|
215
|
+
// Iniciar sesión en background
|
|
216
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
217
|
+
session.startRunning()
|
|
259
218
|
}
|
|
260
|
-
}
|
|
261
219
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
220
|
+
call.resolve([
|
|
221
|
+
"success": true,
|
|
222
|
+
"message": "Preview de cámara iniciado"
|
|
223
|
+
])
|
|
224
|
+
}
|
|
266
225
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
226
|
|
|
270
227
|
@objc func stopPreview(_ call: CAPPluginCall) {
|
|
271
228
|
DispatchQueue.main.async {
|
|
@@ -280,23 +237,19 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
280
237
|
self.videoPreviewLayer = nil
|
|
281
238
|
self.previewView = nil
|
|
282
239
|
|
|
283
|
-
// Restaurar webView a estado normal
|
|
240
|
+
// Restaurar webView a estado normal
|
|
284
241
|
if let webView = self.bridge?.webView as? WKWebView {
|
|
285
242
|
webView.isOpaque = true
|
|
286
|
-
webView.backgroundColor = .white
|
|
243
|
+
webView.backgroundColor = .white
|
|
287
244
|
webView.scrollView.backgroundColor = .white
|
|
288
245
|
webView.scrollView.isOpaque = true
|
|
289
246
|
}
|
|
290
247
|
|
|
291
|
-
print("🛑 Preview detenido - webView restaurado")
|
|
292
248
|
call.resolve()
|
|
293
249
|
}
|
|
294
250
|
}
|
|
295
251
|
|
|
296
|
-
|
|
297
|
-
|
|
298
252
|
// MARK: - Flash
|
|
299
|
-
|
|
300
253
|
@objc func toggleFlash(_ call: CAPPluginCall) {
|
|
301
254
|
guard let enable = call.getBool("enable"),
|
|
302
255
|
let device = AVCaptureDevice.default(for: .video),
|
|
@@ -322,53 +275,50 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
322
275
|
}
|
|
323
276
|
|
|
324
277
|
@objc func getLastGalleryImage(_ call: CAPPluginCall) {
|
|
278
|
+
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
|
|
325
279
|
|
|
326
|
-
|
|
280
|
+
guard status == .authorized || status == .limited else {
|
|
281
|
+
call.reject("Gallery permission not granted")
|
|
282
|
+
return
|
|
283
|
+
}
|
|
327
284
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
285
|
+
let fetchOptions = PHFetchOptions()
|
|
286
|
+
fetchOptions.sortDescriptors = [
|
|
287
|
+
NSSortDescriptor(key: "creationDate", ascending: false)
|
|
288
|
+
]
|
|
289
|
+
fetchOptions.fetchLimit = 1
|
|
332
290
|
|
|
333
|
-
|
|
334
|
-
fetchOptions.sortDescriptors = [
|
|
335
|
-
NSSortDescriptor(key: "creationDate", ascending: false)
|
|
336
|
-
]
|
|
337
|
-
fetchOptions.fetchLimit = 1
|
|
291
|
+
let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions)
|
|
338
292
|
|
|
339
|
-
|
|
293
|
+
guard let asset = assets.firstObject else {
|
|
294
|
+
call.reject("No images found")
|
|
295
|
+
return
|
|
296
|
+
}
|
|
340
297
|
|
|
341
|
-
|
|
342
|
-
|
|
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")
|
|
343
310
|
return
|
|
344
311
|
}
|
|
345
312
|
|
|
346
|
-
let
|
|
347
|
-
let options = PHImageRequestOptions()
|
|
348
|
-
options.isSynchronous = true
|
|
349
|
-
options.deliveryMode = .highQualityFormat
|
|
350
|
-
options.resizeMode = .none
|
|
351
|
-
|
|
352
|
-
imageManager.requestImageDataAndOrientation(
|
|
353
|
-
for: asset,
|
|
354
|
-
options: options
|
|
355
|
-
) { data, _, _, _ in
|
|
313
|
+
let base64 = imageData.base64EncodedString()
|
|
356
314
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
let base64 = imageData.base64EncodedString()
|
|
363
|
-
|
|
364
|
-
var ret = JSObject()
|
|
365
|
-
ret["base64"] = base64
|
|
366
|
-
call.resolve(ret)
|
|
367
|
-
}
|
|
315
|
+
var ret = JSObject()
|
|
316
|
+
ret["base64"] = base64
|
|
317
|
+
call.resolve(ret)
|
|
368
318
|
}
|
|
319
|
+
}
|
|
369
320
|
|
|
370
321
|
// MARK: - Photo Capture
|
|
371
|
-
|
|
372
322
|
@objc func takePhotoBase64(_ call: CAPPluginCall) {
|
|
373
323
|
guard captureSession?.isRunning == true else {
|
|
374
324
|
call.reject("Camera not started")
|
|
@@ -390,7 +340,6 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
390
340
|
}
|
|
391
341
|
|
|
392
342
|
// MARK: - Barcode Scan
|
|
393
|
-
|
|
394
343
|
@objc func startBarcodeScan(_ call: CAPPluginCall) {
|
|
395
344
|
guard let session = captureSession else {
|
|
396
345
|
call.reject("Preview not started")
|
|
@@ -467,8 +416,7 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
467
416
|
|
|
468
417
|
let handler = VNImageRequestHandler(
|
|
469
418
|
cvPixelBuffer: pixelBuffer,
|
|
470
|
-
orientation: CGImagePropertyOrientation.right
|
|
471
|
-
,
|
|
419
|
+
orientation: CGImagePropertyOrientation.right,
|
|
472
420
|
options: [:]
|
|
473
421
|
)
|
|
474
422
|
|
|
@@ -477,12 +425,10 @@ public class CameraModulePlugin: CAPPlugin,CAPBridgedPlugin,AVCaptureVideoDataOu
|
|
|
477
425
|
}
|
|
478
426
|
|
|
479
427
|
// MARK: - UIImagePicker Delegate
|
|
480
|
-
|
|
481
428
|
extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
482
429
|
|
|
483
430
|
public func imagePickerController(_ picker: UIImagePickerController,
|
|
484
431
|
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
|
485
|
-
|
|
486
432
|
picker.dismiss(animated: true)
|
|
487
433
|
|
|
488
434
|
guard let image = info[.originalImage] as? UIImage,
|
|
@@ -508,7 +454,6 @@ extension CameraModulePlugin: UIImagePickerControllerDelegate, UINavigationContr
|
|
|
508
454
|
}
|
|
509
455
|
|
|
510
456
|
// MARK: - UIImage Resize
|
|
511
|
-
|
|
512
457
|
extension UIImage {
|
|
513
458
|
func resized(maxSize: CGFloat) -> UIImage? {
|
|
514
459
|
let maxSide = max(size.width, size.height)
|
|
@@ -523,6 +468,23 @@ extension UIImage {
|
|
|
523
468
|
UIGraphicsEndImageContext()
|
|
524
469
|
return img
|
|
525
470
|
}
|
|
471
|
+
}
|
|
526
472
|
|
|
473
|
+
// MARK: - Photo Delegate (si necesitas esta clase)
|
|
474
|
+
class PhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate {
|
|
475
|
+
private let completion: (String) -> Void
|
|
527
476
|
|
|
528
|
-
|
|
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
|
+
}
|