@capacitor-community/camera-preview 2.1.0 → 3.1.1

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.
@@ -11,20 +11,20 @@ import UIKit
11
11
 
12
12
  class CameraController: NSObject {
13
13
  var captureSession: AVCaptureSession?
14
-
14
+
15
15
  var currentCameraPosition: CameraPosition?
16
-
16
+
17
17
  var frontCamera: AVCaptureDevice?
18
18
  var frontCameraInput: AVCaptureDeviceInput?
19
-
19
+
20
20
  var dataOutput: AVCaptureVideoDataOutput?
21
21
  var photoOutput: AVCapturePhotoOutput?
22
-
22
+
23
23
  var rearCamera: AVCaptureDevice?
24
24
  var rearCameraInput: AVCaptureDeviceInput?
25
-
25
+
26
26
  var previewLayer: AVCaptureVideoPreviewLayer?
27
-
27
+
28
28
  var flashMode = AVCaptureDevice.FlashMode.off
29
29
  var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
30
30
 
@@ -34,12 +34,12 @@ class CameraController: NSObject {
34
34
 
35
35
  var audioDevice: AVCaptureDevice?
36
36
  var audioInput: AVCaptureDeviceInput?
37
-
37
+
38
38
  var zoomFactor: CGFloat = 1.0
39
39
  }
40
40
 
41
41
  extension CameraController {
42
- func prepare(cameraPosition: String, completionHandler: @escaping (Error?) -> Void) {
42
+ func prepare(cameraPosition: String, disableAudio: Bool, completionHandler: @escaping (Error?) -> Void) {
43
43
  func createCaptureSession() {
44
44
  self.captureSession = AVCaptureSession()
45
45
  }
@@ -64,7 +64,9 @@ extension CameraController {
64
64
  camera.unlockForConfiguration()
65
65
  }
66
66
  }
67
- self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
67
+ if disableAudio == false {
68
+ self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
69
+ }
68
70
  }
69
71
 
70
72
  func configureDeviceInputs() throws {
@@ -82,20 +84,21 @@ extension CameraController {
82
84
  if let frontCamera = self.frontCamera {
83
85
  self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
84
86
 
85
- if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) }
86
- else { throw CameraControllerError.inputsAreInvalid }
87
+ if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) } else { throw CameraControllerError.inputsAreInvalid }
87
88
 
88
89
  self.currentCameraPosition = .front
89
90
  }
90
91
  } else { throw CameraControllerError.noCamerasAvailable }
91
92
 
92
93
  // Add audio input
93
- if let audioDevice = self.audioDevice {
94
- self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
95
- if captureSession.canAddInput(self.audioInput!) {
96
- captureSession.addInput(self.audioInput!)
97
- } else {
98
- throw CameraControllerError.inputsAreInvalid
94
+ if disableAudio == false {
95
+ if let audioDevice = self.audioDevice {
96
+ self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
97
+ if captureSession.canAddInput(self.audioInput!) {
98
+ captureSession.addInput(self.audioInput!)
99
+ } else {
100
+ throw CameraControllerError.inputsAreInvalid
101
+ }
99
102
  }
100
103
  }
101
104
  }
@@ -104,7 +107,7 @@ extension CameraController {
104
107
  guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
105
108
 
106
109
  self.photoOutput = AVCapturePhotoOutput()
107
- self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecType.jpeg])], completionHandler: nil)
110
+ self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
108
111
  self.photoOutput?.isHighResolutionCaptureEnabled = self.highResolutionOutput
109
112
  if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
110
113
  captureSession.startRunning()
@@ -135,10 +138,8 @@ extension CameraController {
135
138
  try configureDeviceInputs()
136
139
  try configurePhotoOutput()
137
140
  try configureDataOutput()
138
- //try configureVideoOutput()
139
- }
140
-
141
- catch {
141
+ // try configureVideoOutput()
142
+ } catch {
142
143
  DispatchQueue.main.async {
143
144
  completionHandler(error)
144
145
  }
@@ -147,8 +148,6 @@ extension CameraController {
147
148
  }
148
149
 
149
150
  DispatchQueue.main.async {
150
- self.updateVideoOrientation()
151
-
152
151
  completionHandler(nil)
153
152
  }
154
153
  }
@@ -162,21 +161,23 @@ extension CameraController {
162
161
 
163
162
  view.layer.insertSublayer(self.previewLayer!, at: 0)
164
163
  self.previewLayer?.frame = view.frame
164
+
165
+ updateVideoOrientation()
165
166
  }
166
167
 
167
168
  func setupGestures(target: UIView, enableZoom: Bool) {
168
169
  setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
169
- if (enableZoom) {
170
+ if enableZoom {
170
171
  setupPinchGesture(target: target, selector: #selector(handlePinch(_:)), delegate: self)
171
172
  }
172
173
  }
173
-
174
+
174
175
  func setupTapGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
175
176
  let tapGesture = UITapGestureRecognizer(target: self, action: selector)
176
177
  tapGesture.delegate = delegate
177
178
  target.addGestureRecognizer(tapGesture)
178
179
  }
179
-
180
+
180
181
  func setupPinchGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
181
182
  let pinchGesture = UIPinchGestureRecognizer(target: self, action: selector)
182
183
  pinchGesture.delegate = delegate
@@ -187,36 +188,24 @@ extension CameraController {
187
188
  assert(Thread.isMainThread) // UIApplication.statusBarOrientation requires the main thread.
188
189
 
189
190
  let videoOrientation: AVCaptureVideoOrientation
190
- switch UIDevice.current.orientation {
191
+ switch UIApplication.shared.statusBarOrientation {
191
192
  case .portrait:
192
193
  videoOrientation = .portrait
193
194
  case .landscapeLeft:
194
- videoOrientation = .landscapeRight
195
- case .landscapeRight:
196
195
  videoOrientation = .landscapeLeft
196
+ case .landscapeRight:
197
+ videoOrientation = .landscapeRight
197
198
  case .portraitUpsideDown:
198
199
  videoOrientation = .portraitUpsideDown
199
- case .faceUp, .faceDown, .unknown:
200
+ case .unknown:
200
201
  fallthrough
201
202
  @unknown default:
202
- switch UIApplication.shared.statusBarOrientation {
203
- case .portrait:
204
- videoOrientation = .portrait
205
- case .landscapeLeft:
206
- videoOrientation = .landscapeLeft
207
- case .landscapeRight:
208
- videoOrientation = .landscapeRight
209
- case .portraitUpsideDown:
210
- videoOrientation = .portraitUpsideDown
211
- case .unknown:
212
- fallthrough
213
- @unknown default:
214
- videoOrientation = .portrait
215
- }
203
+ videoOrientation = .portrait
216
204
  }
217
205
 
218
206
  previewLayer?.connection?.videoOrientation = videoOrientation
219
207
  dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
208
+ photoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
220
209
  }
221
210
 
222
211
  func switchCameras() throws {
@@ -227,7 +216,7 @@ extension CameraController {
227
216
  func switchToFrontCamera() throws {
228
217
 
229
218
  guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
230
- let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
219
+ let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
231
220
 
232
221
  self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
233
222
 
@@ -237,9 +226,7 @@ extension CameraController {
237
226
  captureSession.addInput(self.frontCameraInput!)
238
227
 
239
228
  self.currentCameraPosition = .front
240
- }
241
-
242
- else {
229
+ } else {
243
230
  throw CameraControllerError.invalidOperation
244
231
  }
245
232
  }
@@ -247,7 +234,7 @@ extension CameraController {
247
234
  func switchToRearCamera() throws {
248
235
 
249
236
  guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
250
- let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
237
+ let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
251
238
 
252
239
  self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
253
240
 
@@ -257,9 +244,7 @@ extension CameraController {
257
244
  captureSession.addInput(self.rearCameraInput!)
258
245
 
259
246
  self.currentCameraPosition = .rear
260
- }
261
-
262
- else { throw CameraControllerError.invalidOperation }
247
+ } else { throw CameraControllerError.invalidOperation }
263
248
  }
264
249
 
265
250
  switch currentCameraPosition {
@@ -278,7 +263,7 @@ extension CameraController {
278
263
  let settings = AVCapturePhotoSettings()
279
264
 
280
265
  settings.flashMode = self.flashMode
281
- settings.isHighResolutionPhotoEnabled = self.highResolutionOutput;
266
+ settings.isHighResolutionPhotoEnabled = self.highResolutionOutput
282
267
 
283
268
  self.photoOutput?.capturePhoto(with: settings, delegate: self)
284
269
  self.photoCaptureCompletionBlock = completion
@@ -293,23 +278,23 @@ extension CameraController {
293
278
 
294
279
  self.sampleBufferCaptureCompletionBlock = completion
295
280
  }
296
-
281
+
297
282
  func getSupportedFlashModes() throws -> [String] {
298
283
  var currentCamera: AVCaptureDevice?
299
284
  switch currentCameraPosition {
300
- case .front:
301
- currentCamera = self.frontCamera!;
302
- case .rear:
303
- currentCamera = self.rearCamera!;
304
- default: break;
285
+ case .front:
286
+ currentCamera = self.frontCamera!
287
+ case .rear:
288
+ currentCamera = self.rearCamera!
289
+ default: break
305
290
  }
306
-
291
+
307
292
  guard
308
293
  let device = currentCamera
309
294
  else {
310
295
  throw CameraControllerError.noCamerasAvailable
311
296
  }
312
-
297
+
313
298
  var supportedFlashModesAsStrings: [String] = []
314
299
  if device.hasFlash {
315
300
  guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
@@ -319,13 +304,13 @@ extension CameraController {
319
304
  for flashMode in supportedFlashModes {
320
305
  var flashModeValue: String?
321
306
  switch flashMode {
322
- case AVCaptureDevice.FlashMode.off:
323
- flashModeValue = "off"
324
- case AVCaptureDevice.FlashMode.on:
325
- flashModeValue = "on"
326
- case AVCaptureDevice.FlashMode.auto:
327
- flashModeValue = "auto"
328
- default: break;
307
+ case AVCaptureDevice.FlashMode.off:
308
+ flashModeValue = "off"
309
+ case AVCaptureDevice.FlashMode.on:
310
+ flashModeValue = "on"
311
+ case AVCaptureDevice.FlashMode.auto:
312
+ flashModeValue = "auto"
313
+ default: break
329
314
  }
330
315
  if flashModeValue != nil {
331
316
  supportedFlashModesAsStrings.append(flashModeValue!)
@@ -336,38 +321,38 @@ extension CameraController {
336
321
  supportedFlashModesAsStrings.append("torch")
337
322
  }
338
323
  return supportedFlashModesAsStrings
339
-
324
+
340
325
  }
341
-
326
+
342
327
  func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
343
328
  var currentCamera: AVCaptureDevice?
344
329
  switch currentCameraPosition {
345
- case .front:
346
- currentCamera = self.frontCamera!;
347
- case .rear:
348
- currentCamera = self.rearCamera!;
349
- default: break;
330
+ case .front:
331
+ currentCamera = self.frontCamera!
332
+ case .rear:
333
+ currentCamera = self.rearCamera!
334
+ default: break
350
335
  }
351
-
336
+
352
337
  guard let device = currentCamera else {
353
338
  throw CameraControllerError.noCamerasAvailable
354
339
  }
355
-
340
+
356
341
  guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
357
342
  throw CameraControllerError.invalidOperation
358
343
  }
359
344
  if supportedFlashModes.contains(flashMode) {
360
345
  do {
361
346
  try device.lockForConfiguration()
362
-
363
- if(device.hasTorch && device.isTorchAvailable && device.torchMode == AVCaptureDevice.TorchMode.on) {
347
+
348
+ if device.hasTorch && device.isTorchAvailable && device.torchMode == AVCaptureDevice.TorchMode.on {
364
349
  device.torchMode = AVCaptureDevice.TorchMode.off
365
350
  }
366
351
  self.flashMode = flashMode
367
352
  let photoSettings = AVCapturePhotoSettings()
368
353
  photoSettings.flashMode = flashMode
369
354
  self.photoOutput?.photoSettingsForSceneMonitoring = photoSettings
370
-
355
+
371
356
  device.unlockForConfiguration()
372
357
  } catch {
373
358
  throw CameraControllerError.invalidOperation
@@ -376,17 +361,17 @@ extension CameraController {
376
361
  throw CameraControllerError.invalidOperation
377
362
  }
378
363
  }
379
-
364
+
380
365
  func setTorchMode() throws {
381
366
  var currentCamera: AVCaptureDevice?
382
367
  switch currentCameraPosition {
383
- case .front:
384
- currentCamera = self.frontCamera!;
385
- case .rear:
386
- currentCamera = self.rearCamera!;
387
- default: break;
368
+ case .front:
369
+ currentCamera = self.frontCamera!
370
+ case .rear:
371
+ currentCamera = self.rearCamera!
372
+ default: break
388
373
  }
389
-
374
+
390
375
  guard
391
376
  let device = currentCamera,
392
377
  device.hasTorch,
@@ -397,9 +382,9 @@ extension CameraController {
397
382
 
398
383
  do {
399
384
  try device.lockForConfiguration()
400
- if (device.isTorchModeSupported(AVCaptureDevice.TorchMode.on)) {
385
+ if device.isTorchModeSupported(AVCaptureDevice.TorchMode.on) {
401
386
  device.torchMode = AVCaptureDevice.TorchMode.on
402
- } else if (device.isTorchModeSupported(AVCaptureDevice.TorchMode.auto)) {
387
+ } else if device.isTorchModeSupported(AVCaptureDevice.TorchMode.auto) {
403
388
  device.torchMode = AVCaptureDevice.TorchMode.auto
404
389
  } else {
405
390
  device.torchMode = AVCaptureDevice.TorchMode.off
@@ -408,7 +393,7 @@ extension CameraController {
408
393
  } catch {
409
394
  throw CameraControllerError.invalidOperation
410
395
  }
411
-
396
+
412
397
  }
413
398
 
414
399
  func captureVideo(completion: @escaping (URL?, Error?) -> Void) {
@@ -422,10 +407,10 @@ extension CameraController {
422
407
  let finalIdentifier = String(randomIdentifier.prefix(8))
423
408
  let fileName="cpcp_video_"+finalIdentifier+".mp4"
424
409
 
425
- let fileUrl = path.appendingPathComponent(fileName)
410
+ let fileUrl = path.appendingPathComponent(fileName)
426
411
  try? FileManager.default.removeItem(at: fileUrl)
427
412
  /*videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
428
- self.videoRecordCompletionBlock = completion*/
413
+ self.videoRecordCompletionBlock = completion*/
429
414
  }
430
415
 
431
416
  func stopRecording(completion: @escaping (Error?) -> Void) {
@@ -433,32 +418,32 @@ extension CameraController {
433
418
  completion(CameraControllerError.captureSessionIsMissing)
434
419
  return
435
420
  }
436
- //self.videoOutput?.stopRecording()
421
+ // self.videoOutput?.stopRecording()
437
422
  }
438
423
  }
439
424
 
440
425
  extension CameraController: UIGestureRecognizerDelegate {
441
426
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
442
- return true;
427
+ return true
443
428
  }
444
-
429
+
445
430
  @objc
446
431
  func handleTap(_ tap: UITapGestureRecognizer) {
447
432
  guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
448
-
433
+
449
434
  let point = tap.location(in: tap.view)
450
435
  let devicePoint = self.previewLayer?.captureDevicePointConverted(fromLayerPoint: point)
451
-
436
+
452
437
  do {
453
438
  try device.lockForConfiguration()
454
439
  defer { device.unlockForConfiguration() }
455
-
440
+
456
441
  let focusMode = AVCaptureDevice.FocusMode.autoFocus
457
442
  if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) {
458
443
  device.focusPointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
459
444
  device.focusMode = focusMode
460
445
  }
461
-
446
+
462
447
  let exposureMode = AVCaptureDevice.ExposureMode.autoExpose
463
448
  if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) {
464
449
  device.exposurePointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
@@ -468,24 +453,24 @@ extension CameraController: UIGestureRecognizerDelegate {
468
453
  debugPrint(error)
469
454
  }
470
455
  }
471
-
456
+
472
457
  @objc
473
458
  private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
474
459
  guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
475
-
460
+
476
461
  func minMaxZoom(_ factor: CGFloat) -> CGFloat { return max(1.0, min(factor, device.activeFormat.videoMaxZoomFactor)) }
477
-
462
+
478
463
  func update(scale factor: CGFloat) {
479
464
  do {
480
465
  try device.lockForConfiguration()
481
466
  defer { device.unlockForConfiguration() }
482
-
467
+
483
468
  device.videoZoomFactor = factor
484
469
  } catch {
485
470
  debugPrint(error)
486
471
  }
487
472
  }
488
-
473
+
489
474
  switch pinch.state {
490
475
  case .began: fallthrough
491
476
  case .changed:
@@ -501,14 +486,10 @@ extension CameraController: UIGestureRecognizerDelegate {
501
486
  extension CameraController: AVCapturePhotoCaptureDelegate {
502
487
  public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
503
488
  resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
504
- if let error = error { self.photoCaptureCompletionBlock?(nil, error) }
505
-
506
- else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
507
- let image = UIImage(data: data) {
489
+ if let error = error { self.photoCaptureCompletionBlock?(nil, error) } else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
490
+ let image = UIImage(data: data) {
508
491
  self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
509
- }
510
-
511
- else {
492
+ } else {
512
493
  self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
513
494
  }
514
495
  }
@@ -556,9 +537,6 @@ extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
556
537
  }
557
538
  }
558
539
 
559
-
560
-
561
-
562
540
  enum CameraControllerError: Swift.Error {
563
541
  case captureSessionAlreadyRunning
564
542
  case captureSessionIsMissing
@@ -596,21 +574,21 @@ extension CameraControllerError: LocalizedError {
596
574
  extension UIImage {
597
575
 
598
576
  func fixedOrientation() -> UIImage? {
599
-
577
+
600
578
  guard imageOrientation != UIImage.Orientation.up else {
601
- //This is default orientation, don't need to do anything
579
+ // This is default orientation, don't need to do anything
602
580
  return self.copy() as? UIImage
603
581
  }
604
-
582
+
605
583
  guard let cgImage = self.cgImage else {
606
- //CGImage is not available
584
+ // CGImage is not available
607
585
  return nil
608
586
  }
609
587
 
610
588
  guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
611
- return nil //Not able to create CGContext
589
+ return nil // Not able to create CGContext
612
590
  }
613
-
591
+
614
592
  var transform: CGAffineTransform = CGAffineTransform.identity
615
593
  switch imageOrientation {
616
594
  case .down, .downMirrored:
@@ -631,8 +609,8 @@ extension UIImage {
631
609
  case .up, .upMirrored:
632
610
  break
633
611
  }
634
-
635
- //Flip image one more time if needed to, this is to prevent flipped image
612
+
613
+ // Flip image one more time if needed to, this is to prevent flipped image
636
614
  switch imageOrientation {
637
615
  case .upMirrored, .downMirrored:
638
616
  transform.translatedBy(x: size.width, y: 0)
@@ -644,9 +622,9 @@ extension UIImage {
644
622
  case .up, .down, .left, .right:
645
623
  break
646
624
  }
647
-
625
+
648
626
  ctx.concatenate(transform)
649
-
627
+
650
628
  switch imageOrientation {
651
629
  case .left, .leftMirrored, .right, .rightMirrored:
652
630
  ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
@@ -662,9 +640,9 @@ extension UIImage {
662
640
  extension CameraController: AVCaptureFileOutputRecordingDelegate {
663
641
  func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
664
642
  /*if error == nil {
665
- self.videoRecordCompletionBlock?(outputFileURL, nil)
666
- } else {
667
- self.videoRecordCompletionBlock?(nil, error)
668
- }*/
643
+ self.videoRecordCompletionBlock?(outputFileURL, nil)
644
+ } else {
645
+ self.videoRecordCompletionBlock?(nil, error)
646
+ }*/
669
647
  }
670
648
  }