@momo-kits/camerakit 0.161.1-beta.1 → 0.161.2-beta.16

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.
@@ -232,7 +232,7 @@ static id CKConvertFollyDynamicToId(const folly::dynamic &dyn)
232
232
  }
233
233
  id zoomMode = CKConvertFollyDynamicToId(newProps.zoomMode);
234
234
  if (zoomMode != nil) {
235
- _view.zoomMode = [focusMode isEqualToString:@"on"] ? CKZoomModeOn : CKZoomModeOff;
235
+ _view.zoomMode = [zoomMode isEqualToString:@"on"] ? CKZoomModeOn : CKZoomModeOff;
236
236
  [changedProps addObject:@"zoomMode"];
237
237
  }
238
238
  id zoom = CKConvertFollyDynamicToId(newProps.zoom);
@@ -18,12 +18,8 @@ import Foundation
18
18
  return CameraView()
19
19
  }
20
20
 
21
- @objc public static func capture(camera: CameraView,
22
- options: NSDictionary,
23
- resolve: @escaping RCTPromiseResolveBlock,
24
- reject: @escaping RCTPromiseRejectBlock) {
25
- camera.capture(options as! [String: Any], onSuccess: { resolve($0) },
26
- onError: { reject("capture_error", $0, nil) })
21
+ @objc public static func capture(camera: CameraView, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
22
+ camera.capture((options as? [String: Any]) ?? [:], onSuccess: { resolve($0) }, onError: { reject("capture_error", $0, nil) })
27
23
  }
28
24
 
29
25
  @objc public static func checkDeviceCameraAuthorizationStatus(_ resolve: @escaping RCTPromiseResolveBlock,
@@ -361,8 +361,8 @@ public class CameraView: UIView {
361
361
  let features = detector?.features(in: ciImage)
362
362
 
363
363
  if let firstFeature = features?.first as? CIQRCodeFeature {
364
- if (firstFeature.messageString != nil ) {
365
- self.onBarcodeRead(barcode: firstFeature.messageString!, codeFormat: CodeFormat.qr)
364
+ if let messageString = firstFeature.messageString {
365
+ self.onBarcodeRead(barcode: messageString, codeFormat: CodeFormat.qr)
366
366
  }
367
367
  return
368
368
  }
@@ -22,19 +22,19 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
22
22
 
23
23
  private let cameraPreview = RealPreviewView(frame: .zero)
24
24
  private let session = AVCaptureSession()
25
- // Communicate with the session and other session objects on this queue.
26
- private let sessionQueue = DispatchQueue(label: "com.tesla.react-native-camera-kit")
27
-
25
+ private static let sharedSessionQueue = DispatchQueue(label: "com.tesla.react-native-camera-kit.session")
26
+ private let sessionQueue = RealCamera.sharedSessionQueue
27
+
28
28
  // utilities
29
29
  private var setupResult: SetupResult = .notStarted
30
30
  private var configurationDepth: Int = 0 // Tracks nested beginConfiguration/commitConfiguration calls
31
31
  private var pendingStop: Bool = false // Queues stop request during configuration
32
32
  private var backgroundRecordingId: UIBackgroundTaskIdentifier = .invalid
33
-
33
+
34
34
  private var videoDeviceInput: AVCaptureDeviceInput?
35
35
  private let photoOutput = AVCapturePhotoOutput()
36
36
  private let metadataOutput = AVCaptureMetadataOutput()
37
-
37
+
38
38
  private var resizeMode: ResizeMode = .cover
39
39
  private var flashMode: FlashMode = .auto
40
40
  private var torchMode: TorchMode = .off
@@ -49,25 +49,27 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
49
49
  private var lastOnZoom: Double?
50
50
  private var zoom: Double?
51
51
  private var maxZoom: Double?
52
-
52
+
53
53
  private var deviceOrientation = UIDeviceOrientation.unknown
54
54
  private var motionManager: CMMotionManager?
55
-
55
+
56
56
  // KVO observation
57
57
  private var adjustingFocusObservation: NSKeyValueObservation?
58
58
 
59
+ private var notificationObservers: [NSObjectProtocol] = []
60
+
59
61
  // Keep delegate objects in memory to avoid collecting them before photo capturing finishes
60
62
  private var inProgressPhotoCaptureDelegates = [Int64: PhotoCaptureDelegate]()
61
-
63
+
62
64
  private var onTextRead: ((_ text: String) -> Void)?
63
65
  private let videoDataOutput = AVCaptureVideoDataOutput()
64
66
  private var textRequest: VNRecognizeTextRequest?
65
67
  private var textDetectionEnabled = false
66
68
  private var lastTextProcess = Date.distantPast
67
69
  private let textThrottle: TimeInterval = 0.35 // seconds
68
-
70
+
69
71
  private var zoomStartedAt: Double = 1.0
70
-
72
+
71
73
  // MARK: - Lifecycle
72
74
 
73
75
  func cameraRemovedFromSuperview() {
@@ -93,7 +95,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
93
95
  UIDevice.current.endGeneratingDeviceOrientationNotifications()
94
96
  #endif
95
97
  }
96
-
98
+
97
99
  deinit {
98
100
  removeObservers()
99
101
 
@@ -108,7 +110,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
108
110
  }
109
111
  }
110
112
  }
111
-
113
+
112
114
  // MARK: - Public
113
115
 
114
116
  func setup(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) {
@@ -222,7 +224,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
222
224
 
223
225
  return .success
224
226
  }
225
-
227
+
226
228
  // MARK: - Pause / Resume non-essential outputs
227
229
 
228
230
  /// Disables OCR + barcode work while a photo is captured.
@@ -259,14 +261,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
259
261
  }
260
262
  }
261
263
  }
262
-
264
+
263
265
  func zoomPinchStart() {
264
266
  sessionQueue.async {
265
267
  guard let videoDevice = self.videoDeviceInput?.device else { return }
266
268
  self.zoomStartedAt = videoDevice.videoZoomFactor
267
269
  }
268
270
  }
269
-
271
+
270
272
  func zoomPinchChange(pinchScale: CGFloat) {
271
273
  guard !pinchScale.isNaN else { return }
272
274
 
@@ -275,7 +277,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
275
277
 
276
278
  let desiredZoomFactor = (self.zoomStartedAt / self.defaultZoomFactor(for: videoDevice)) * pinchScale
277
279
  let zoomForDevice = self.getValidZoom(forDevice: videoDevice, zoom: desiredZoomFactor)
278
-
280
+
279
281
  if zoomForDevice != self.normalizedZoom(for: videoDevice) {
280
282
  // Only trigger zoom changes if it's an uncontrolled component (zoom isn't manually set)
281
283
  // otherwise it's likely to cause issues inf. loops
@@ -286,14 +288,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
286
288
  }
287
289
  }
288
290
  }
289
-
291
+
290
292
  func update(maxZoom: Double?) {
291
293
  self.maxZoom = maxZoom
292
294
 
293
295
  // Re-update zoom value in case the max was increased
294
296
  self.update(zoom: self.zoom)
295
297
  }
296
-
298
+
297
299
  func update(zoom: Double?) {
298
300
  sessionQueue.async {
299
301
  self.zoom = zoom
@@ -304,7 +306,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
304
306
  self.setZoomFor(videoDevice, to: zoomForDevice)
305
307
  }
306
308
  }
307
-
309
+
308
310
  /**
309
311
  `desiredZoom` can be nil when we want to notify what the zoom factor really is
310
312
  */
@@ -325,11 +327,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
325
327
  lastOnZoom = desiredOrCameraZoom
326
328
  self.onZoomCallback?(["zoom": desiredOrCameraZoom])
327
329
  }
328
-
330
+
329
331
  func update(onZoom: RCTDirectEventBlock?) {
330
332
  self.onZoomCallback = onZoom
331
333
  }
332
-
334
+
333
335
  func focus(at touchPoint: CGPoint, focusBehavior: FocusBehavior) {
334
336
  DispatchQueue.main.async {
335
337
  let devicePoint = self.cameraPreview.previewLayer.captureDevicePointConverted(fromLayerPoint: touchPoint)
@@ -344,7 +346,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
344
346
  self.resetFocus = nil
345
347
  self.focusFinished = nil
346
348
  }
347
-
349
+
348
350
  do {
349
351
  try videoDevice.lockForConfiguration()
350
352
 
@@ -367,11 +369,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
367
369
  }
368
370
  }
369
371
  }
370
-
372
+
371
373
  func update(onOrientationChange: RCTDirectEventBlock?) {
372
374
  self.onOrientationChange = onOrientationChange
373
375
  }
374
-
376
+
375
377
  func update(torchMode: TorchMode) {
376
378
  sessionQueue.async {
377
379
  self.torchMode = torchMode
@@ -388,23 +390,23 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
388
390
  }
389
391
  }
390
392
  }
391
-
393
+
392
394
  func update(flashMode: FlashMode) {
393
395
  self.flashMode = flashMode
394
396
  }
395
-
397
+
396
398
  func update(maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?) {
397
399
  guard #available(iOS 13.0, *) else { return }
398
- guard maxPhotoQualityPrioritization != self.maxPhotoQualityPrioritization else { return }
399
400
  sessionQueue.async { [weak self] in
400
401
  guard let self else { return }
402
+ guard maxPhotoQualityPrioritization != self.maxPhotoQualityPrioritization else { return }
401
403
  self.beginConfiguration()
402
404
  defer { self.commitConfiguration() }
403
405
  self.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization
404
406
  self.photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization?.avQualityPrioritization ?? .balanced
405
407
  }
406
408
  }
407
-
409
+
408
410
  func update(cameraType: CameraType) {
409
411
  sessionQueue.async { [weak self] in
410
412
  guard let self else { return }
@@ -426,7 +428,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
426
428
 
427
429
  // Remove the existing device input first, since using the front and back camera simultaneously is not supported.
428
430
  self.session.removeInput(currentViewDeviceInput)
429
-
431
+
430
432
  if self.session.canAddInput(videoDeviceInput) {
431
433
  self.session.addInput(videoDeviceInput)
432
434
  self.resetZoom(forDevice: videoDevice)
@@ -435,14 +437,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
435
437
  // If it fails, put back current camera
436
438
  self.session.addInput(currentViewDeviceInput)
437
439
  }
438
-
440
+
439
441
  self.addObservers()
440
442
 
441
443
  // We need to reapply the configuration after reloading the camera
442
444
  self.update(torchMode: self.torchMode)
443
445
  }
444
446
  }
445
-
447
+
446
448
  func update(resizeMode: ResizeMode) {
447
449
  DispatchQueue.main.async {
448
450
  switch resizeMode {
@@ -453,7 +455,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
453
455
  }
454
456
  }
455
457
  }
456
-
458
+
457
459
  func capturePicture(onWillCapture: @escaping () -> Void,
458
460
  onSuccess: @escaping (_ imageData: Data, _ thumbnailData: Data?, _ dimensions: CMVideoDimensions) -> Void,
459
461
  onError: @escaping (_ message: String) -> Void) {
@@ -462,36 +464,41 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
462
464
  entering the session queue. Do this to ensure that UI elements are accessed on
463
465
  the main thread and session configuration is done on the session queue.
464
466
  */
465
-
467
+
466
468
  DispatchQueue.main.async { [weak self] in
467
469
  guard let self = self else {
468
470
  onError("Camera was deallocated")
469
471
  return
470
472
  }
471
-
473
+
472
474
  let videoPreviewLayerOrientation =
473
475
  self.videoOrientation(from: self.deviceOrientation) ?? self.cameraPreview.previewLayer.connection?.videoOrientation
474
-
476
+
475
477
  self.sessionQueue.async { [weak self] in
476
478
  guard let self = self else {
477
479
  onError("Camera was deallocated")
478
480
  return
479
481
  }
480
482
 
483
+ guard self.inProgressPhotoCaptureDelegates.isEmpty else {
484
+ onError("Capture already in progress")
485
+ return
486
+ }
487
+
481
488
  // Validate that the session is ready for photo capture
482
489
  guard self.session.isRunning else {
483
490
  print("Cannot capture photo: session is not running")
484
491
  onError("Camera session is not running")
485
492
  return
486
493
  }
487
-
494
+
488
495
  // Ensure photo output has an active video connection
489
496
  guard let photoOutputConnection = self.photoOutput.connection(with: .video) else {
490
497
  print("Cannot capture photo: no video connection available")
491
498
  onError("Camera connection is not available")
492
499
  return
493
500
  }
494
-
501
+
495
502
  // Verify the connection is active and enabled
496
503
  guard photoOutputConnection.isActive && photoOutputConnection.isEnabled else {
497
504
  print("Cannot capture photo: video connection is not active or enabled")
@@ -520,32 +527,30 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
520
527
  with: settings,
521
528
  onWillCapture: onWillCapture,
522
529
  onCaptureSuccess: { [weak self] uniqueID, imageData, thumbnailData, dimensions in
523
- // Use weak self to prevent crash if camera is deallocated during capture
524
- self?.inProgressPhotoCaptureDelegates[uniqueID] = nil
530
+ self?.sessionQueue.async { self?.inProgressPhotoCaptureDelegates[uniqueID] = nil }
525
531
  onSuccess(imageData, thumbnailData, dimensions)
526
532
  self?.resumeNonEssentialOutputs()
527
533
  },
528
534
  onCaptureError: { [weak self] uniqueID, errorMessage in
529
- // Use weak self to prevent crash if camera is deallocated during capture
530
- self?.inProgressPhotoCaptureDelegates[uniqueID] = nil
535
+ self?.sessionQueue.async { self?.inProgressPhotoCaptureDelegates[uniqueID] = nil }
531
536
  onError(errorMessage)
532
537
  self?.resumeNonEssentialOutputs()
533
538
  }
534
539
  )
535
-
540
+
536
541
  self.inProgressPhotoCaptureDelegates[photoCaptureDelegate.requestedPhotoSettings.uniqueID] = photoCaptureDelegate
537
542
  self.photoOutput.capturePhoto(with: settings, delegate: photoCaptureDelegate)
538
543
  }
539
544
  }
540
545
  }
541
-
546
+
542
547
  // MARK: - Barcode scanning
543
548
  func isBarcodeScannerEnabled(_ isEnabled: Bool,
544
549
  supportedBarcodeTypes supportedBarcodeType: [CodeFormat],
545
550
  onBarcodeRead: ((_ barcode: String,_ codeFormat:CodeFormat) -> Void)?) {
546
551
  sessionQueue.async {
547
552
  self.onBarcodeRead = onBarcodeRead
548
-
553
+
549
554
  let availableTypes = self.metadataOutput.availableMetadataObjectTypes
550
555
  let newTypes: [AVMetadataObject.ObjectType]
551
556
  if isEnabled && onBarcodeRead != nil {
@@ -562,11 +567,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
562
567
  }
563
568
  }
564
569
  }
565
-
570
+
566
571
  func update(barcodeFrameSize: CGSize?) {
567
572
  self.barcodeFrameSize = barcodeFrameSize
568
573
  }
569
-
574
+
570
575
  func update(scannerFrameSize: CGRect?) {
571
576
  guard self.scannerFrameSize != scannerFrameSize else { return }
572
577
  self.sessionQueue.async {
@@ -574,11 +579,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
574
579
  if !self.session.isRunning {
575
580
  return
576
581
  }
577
-
582
+
578
583
  DispatchQueue.main.async {
579
584
  var visibleRect: CGRect?
580
- if scannerFrameSize != nil && scannerFrameSize != .zero {
581
- visibleRect = self.cameraPreview.previewLayer.metadataOutputRectConverted(fromLayerRect: scannerFrameSize!)
585
+ if let scannerFrameSize, scannerFrameSize != .zero {
586
+ visibleRect = self.cameraPreview.previewLayer.metadataOutputRectConverted(fromLayerRect: scannerFrameSize)
582
587
  }
583
588
 
584
589
  self.sessionQueue.async {
@@ -593,7 +598,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
593
598
  }
594
599
  }
595
600
  }
596
-
601
+
597
602
 
598
603
  func isTextDetectionEnabled(_ isEnabled: Bool, onTextRead: ((String) -> Void)?) {
599
604
  sessionQueue.async {
@@ -608,26 +613,26 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
608
613
  self.textRequest?.recognitionLanguages = ["en", "fr", "de", "es", "vi"]
609
614
  self.textRequest?.recognitionLevel = .accurate
610
615
  self.textRequest?.usesLanguageCorrection = false
611
-
616
+
612
617
  self.videoDataOutput.alwaysDiscardsLateVideoFrames = true
613
618
  self.videoDataOutput.setSampleBufferDelegate(self, queue: globalOCRQueue)
614
619
  self.videoDataOutput.videoSettings = [
615
620
  kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
616
621
  ]
617
-
622
+
618
623
  } else {
619
624
  self.textRequest = nil
620
625
  }
621
626
  }
622
627
  }
623
-
628
+
624
629
  // AVCaptureVideoDataOutputSampleBufferDelegate
625
630
  func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
626
631
  guard textDetectionEnabled, let request = textRequest else { return }
627
632
  let now = Date()
628
633
  if now.timeIntervalSince(lastTextProcess) < textThrottle { return }
629
634
  lastTextProcess = now
630
-
635
+
631
636
  globalOCRQueue.async {
632
637
  guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
633
638
  var requestOptions: [VNImageOption: Any] = [:]
@@ -640,7 +645,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
640
645
  }
641
646
  }
642
647
  }
643
-
648
+
644
649
  // MARK: - AVCaptureMetadataOutputObjectsDelegate
645
650
 
646
651
  func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
@@ -654,7 +659,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
654
659
 
655
660
  onBarcodeRead?(codeStringValue,barcodeType)
656
661
  }
657
-
662
+
658
663
  // MARK: - Private
659
664
 
660
665
  private func videoOrientation(from deviceOrientation: UIDeviceOrientation) -> AVCaptureVideoOrientation? {
@@ -673,7 +678,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
673
678
  @unknown default: return nil
674
679
  }
675
680
  }
676
-
681
+
677
682
  private func videoOrientation(from interfaceOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
678
683
  switch interfaceOrientation {
679
684
  case .portrait:
@@ -688,14 +693,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
688
693
  @unknown default: return .portrait
689
694
  }
690
695
  }
691
-
696
+
692
697
  private func getBestDevice(for cameraType: CameraType) -> AVCaptureDevice? {
693
698
  if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: cameraType.avPosition) {
694
699
  return device // single-lens/physical device
695
700
  }
696
701
  return nil
697
702
  }
698
-
703
+
699
704
  private func defaultZoomFactor(for videoDevice: AVCaptureDevice) -> CGFloat {
700
705
  let fallback = 1.0
701
706
  guard #available(iOS 13.0, *) else { return fallback }
@@ -756,7 +761,9 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
756
761
  motionManager = CMMotionManager()
757
762
  motionManager?.accelerometerUpdateInterval = 0.2
758
763
  motionManager?.gyroUpdateInterval = 0.2
759
- motionManager?.startAccelerometerUpdates(to: OperationQueue(), withHandler: { (accelerometerData, error) -> Void in
764
+
765
+ motionManager?.startAccelerometerUpdates(to: OperationQueue(), withHandler: { [weak self] (accelerometerData, error) -> Void in
766
+ guard let self else { return }
760
767
  guard error == nil else {
761
768
  print("\(error!)")
762
769
  return
@@ -767,12 +774,13 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
767
774
  }
768
775
 
769
776
  guard let newOrientation = self.deviceOrientation(from: accelerometerData.acceleration),
770
- newOrientation != self.deviceOrientation else {
777
+ newOrientation != self.deviceOrientation,
778
+ let orientation = Orientation(from: newOrientation) else {
771
779
  return
772
780
  }
773
781
 
774
782
  self.deviceOrientation = newOrientation
775
- self.onOrientationChange?(["orientation": Orientation.init(from: newOrientation)!.rawValue])
783
+ self.onOrientationChange?(["orientation": orientation.rawValue])
776
784
  })
777
785
  #endif
778
786
  }
@@ -797,30 +805,34 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
797
805
  // MARK: Private observers
798
806
 
799
807
  private func addObservers() {
800
- guard adjustingFocusObservation == nil else { return }
808
+ if adjustingFocusObservation == nil {
809
+ adjustingFocusObservation = videoDeviceInput?.device.observe(\.isAdjustingFocus,
810
+ options: .new,
811
+ changeHandler: { [weak self] _, change in
812
+ guard let self, let isFocusing = change.newValue else { return }
801
813
 
802
- adjustingFocusObservation = videoDeviceInput?.device.observe(\.isAdjustingFocus,
803
- options: .new,
804
- changeHandler: { [weak self] _, change in
805
- guard let self, let isFocusing = change.newValue else { return }
806
-
807
- self.isAdjustingFocus(isFocusing: isFocusing)
808
- })
814
+ self.isAdjustingFocus(isFocusing: isFocusing)
815
+ })
816
+ }
809
817
 
810
- NotificationCenter.default.addObserver(forName: .AVCaptureDeviceSubjectAreaDidChange,
811
- object: videoDeviceInput?.device,
812
- queue: nil,
813
- using: { [weak self] notification in self?.subjectAreaDidChange(notification: notification) })
814
- NotificationCenter.default.addObserver(forName: .AVCaptureSessionRuntimeError,
815
- object: session,
816
- queue: nil,
817
- using: { [weak self] notification in self?.sessionRuntimeError(notification: notification) })
818
+ guard notificationObservers.isEmpty else { return }
818
819
 
820
+ let subjectAreaToken = NotificationCenter.default.addObserver(
821
+ forName: .AVCaptureDeviceSubjectAreaDidChange,
822
+ object: videoDeviceInput?.device,
823
+ queue: nil,
824
+ using: { [weak self] notification in self?.subjectAreaDidChange(notification: notification) })
825
+ let runtimeErrorToken = NotificationCenter.default.addObserver(
826
+ forName: .AVCaptureSessionRuntimeError,
827
+ object: session,
828
+ queue: nil,
829
+ using: { [weak self] notification in self?.sessionRuntimeError(notification: notification) })
830
+ notificationObservers = [subjectAreaToken, runtimeErrorToken]
819
831
  }
820
832
 
821
833
  private func removeObservers() {
822
- // swiftlint:disable:next notification_center_detachment
823
- NotificationCenter.default.removeObserver(self)
834
+ notificationObservers.forEach { NotificationCenter.default.removeObserver($0) }
835
+ notificationObservers.removeAll()
824
836
 
825
837
  adjustingFocusObservation?.invalidate()
826
838
  adjustingFocusObservation = nil
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
1
  {
2
- "name": "@momo-kits/camerakit",
3
- "version": "0.161.1-beta.1",
4
- "repository": {
5
- "type": "git",
6
- "url": "https://github.com/teslamotors/react-native-camera-kit.git"
7
- },
8
- "publishConfig": {
9
- "registry": "https://registry.npmjs.org/"
10
- },
11
- "description": "A high performance, fully featured, rock solid camera library for React Native applications",
12
- "nativePackage": true,
13
- "scripts": {
14
- "build": "echo",
15
- "test": "jest",
16
- "lint": "yarn eslint -c .eslintrc.js"
17
- },
18
- "main": "./src/index.ts",
19
- "dependencies": {},
20
- "license": "MIT",
21
- "devDependencies": {
22
- "react": "19.0.0",
23
- "react-native": "0.80.1"
24
- },
25
- "peerDependencies": {
26
- "react": "*",
27
- "react-native": "*"
28
- },
29
- "engines": {
30
- "node": ">=18"
31
- },
32
- "codegenConfig": {
33
- "name": "rncamerakit_specs",
34
- "type": "all",
35
- "jsSrcsDir": "src/specs",
36
- "android": {
37
- "javaPackageName": "com.rncamerakit"
38
- }
39
- },
40
- "packageManager": "yarn@1.22.22"
41
- }
2
+ "name": "@momo-kits/camerakit",
3
+ "version": "0.161.2-beta.16",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/teslamotors/react-native-camera-kit.git"
7
+ },
8
+ "publishConfig": {
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "description": "A high performance, fully featured, rock solid camera library for React Native applications",
12
+ "nativePackage": true,
13
+ "scripts": {
14
+ "build": "echo",
15
+ "test": "jest",
16
+ "lint": "yarn eslint -c .eslintrc.js"
17
+ },
18
+ "main": "./src/index.ts",
19
+ "dependencies": {},
20
+ "license": "MIT",
21
+ "devDependencies": {
22
+ "react": "19.0.0",
23
+ "react-native": "0.80.1"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "*",
27
+ "react-native": "*"
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "codegenConfig": {
33
+ "name": "rncamerakit_specs",
34
+ "type": "all",
35
+ "jsSrcsDir": "src/specs",
36
+ "android": {
37
+ "javaPackageName": "com.rncamerakit"
38
+ }
39
+ },
40
+ "packageManager": "yarn@1.22.22"
41
+ }