1// Copyright 2018-present 650 Industries. All rights reserved. 2 3#import <EXBarCodeScanner/EXBarCodeScannerView.h> 4#import <EXBarCodeScanner/EXBarCodeScanner.h> 5#import <EXBarCodeScanner/EXBarCodeScannerUtils.h> 6#import <EXPermissionsInterface/EXPermissionsInterface.h> 7#import <EXCore/EXAppLifecycleService.h> 8#import <EXCore/EXUtilities.h> 9 10@interface EXBarCodeScannerView () 11 12@property (nonatomic, strong) dispatch_queue_t sessionQueue; 13@property (nonatomic, strong) AVCaptureSession *session; 14@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput; 15@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput; 16@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; 17@property (nonatomic, strong) id runtimeErrorHandlingObserver; 18@property (nonatomic, strong) EXBarCodeScanner *barCodeScanner; 19 20@property (nonatomic, weak) EXModuleRegistry *moduleRegistry; 21@property (nonatomic, weak) id<EXPermissionsInterface> permissionsManager; 22@property (nonatomic, weak) id<EXAppLifecycleService> lifecycleManager; 23 24@property (nonatomic, assign, getter=isSessionPaused) BOOL paused; 25 26@property (nonatomic, copy) EXDirectEventBlock onCameraReady; 27@property (nonatomic, copy) EXDirectEventBlock onMountError; 28@property (nonatomic, copy) EXDirectEventBlock onBarCodeScanned; 29 30@end 31 32@implementation EXBarCodeScannerView 33 34- (instancetype)initWithModuleRegistry:(EXModuleRegistry *)moduleRegistry 35{ 36 if ((self = [super init])) { 37 _presetCamera = AVCaptureDevicePositionBack; 38 _moduleRegistry = moduleRegistry; 39 _session = [AVCaptureSession new]; 40 _sessionQueue = dispatch_queue_create("barCodeScannerQueue", DISPATCH_QUEUE_SERIAL); 41 _lifecycleManager = [moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleListener)]; 42 _permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(EXPermissionsInterface)]; 43 _barCodeScanner = [self createBarCodeScanner]; 44 45#if !(TARGET_IPHONE_SIMULATOR) 46 _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; 47 _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 48 _previewLayer.needsDisplayOnBoundsChange = YES; 49#endif 50 _paused = NO; 51 52 [_lifecycleManager registerAppLifecycleListener:self]; 53 [[NSNotificationCenter defaultCenter] addObserver:self 54 selector:@selector(orientationChanged:) 55 name:UIDeviceOrientationDidChangeNotification 56 object:nil]; 57 58 [self changePreviewOrientation:[UIApplication sharedApplication].statusBarOrientation]; 59 [self initializeSession]; 60 } 61 return self; 62} 63 64# pragma mark - events 65 66- (void)onReady 67{ 68 if (_onCameraReady) { 69 _onCameraReady(nil); 70 } 71} 72 73- (void)onMountingError:(NSDictionary *)event 74{ 75 if (_onMountError) { 76 _onMountError(event); 77 } 78} 79 80- (void)onBarCodeScanned:(NSDictionary *)event 81{ 82 if (_onBarCodeScanned) { 83 _onBarCodeScanned(event); 84 } 85} 86 87# pragma mark - JS properties setters 88 89- (void)setPresetCamera:(NSInteger)presetCamera 90{ 91 if (_presetCamera == presetCamera) { 92 return; 93 } 94 _presetCamera = presetCamera; 95 EX_WEAKIFY(self); 96 dispatch_async(_sessionQueue, ^{ 97 EX_ENSURE_STRONGIFY(self); 98 [self initializeSession]; 99 }); 100} 101 102- (void)setBarCodeTypes:(NSArray *)barCodeTypes 103{ 104 _barCodeTypes = barCodeTypes; 105 [_barCodeScanner setSettings:@{ 106 @"barCodeTypes": barCodeTypes, 107 }]; 108} 109 110# pragma mark - lifecycle 111 112- (void)layoutSubviews 113{ 114 [super layoutSubviews]; 115 _previewLayer.frame = self.bounds; 116 [self setBackgroundColor:[UIColor blackColor]]; 117 [self.layer insertSublayer:_previewLayer atIndex:0]; 118} 119 120- (void)removeFromSuperview 121{ 122 [_lifecycleManager unregisterAppLifecycleListener:self]; 123 [self stopSession]; 124 [super removeFromSuperview]; 125 [[NSNotificationCenter defaultCenter] removeObserver:self 126 name:UIDeviceOrientationDidChangeNotification 127 object:nil]; 128} 129 130- (void)onAppForegrounded 131{ 132 if (![_session isRunning] && [self isSessionPaused]) { 133 _paused = NO; 134 EX_WEAKIFY(self); 135 dispatch_async(_sessionQueue, ^{ 136 EX_ENSURE_STRONGIFY(self); 137 [self.session startRunning]; 138 }); 139 } 140} 141 142- (void)onAppBackgrounded 143{ 144 if ([_session isRunning] && ![self isSessionPaused]) { 145 _paused = YES; 146 EX_WEAKIFY(self); 147 dispatch_async(_sessionQueue, ^{ 148 EX_ENSURE_STRONGIFY(self); 149 [self.session stopRunning]; 150 }); 151 } 152} 153 154# pragma mark - orientation 155 156- (void)orientationChanged:(NSNotification *)notification 157{ 158 UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 159 [self changePreviewOrientation:orientation]; 160} 161 162- (void)changePreviewOrientation:(UIInterfaceOrientation)orientation 163{ 164 EX_WEAKIFY(self); 165 AVCaptureVideoOrientation videoOrientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:orientation]; 166 [EXUtilities performSynchronouslyOnMainThread:^{ 167 EX_ENSURE_STRONGIFY(self); 168 if (self.previewLayer.connection.isVideoOrientationSupported) { 169 [self.previewLayer.connection setVideoOrientation:videoOrientation]; 170 } 171 }]; 172} 173 174# pragma mark - session 175 176- (BOOL)ensurePermissionsGranted 177{ 178 NSDictionary *cameraPermissions = [_permissionsManager getPermissionsForResource:@"camera"]; 179 if (![cameraPermissions[@"status"] isEqualToString:@"granted"]) { 180 [self onMountingError:@{@"message": @"Camera permissions not granted - component could not be rendered."}]; 181 return FALSE; 182 } 183 return TRUE; 184} 185 186- (void)initializeSession 187{ 188 if (_videoCaptureDeviceInput.device.position == _presetCamera) { 189 return; 190 } 191 192 __block UIInterfaceOrientation interfaceOrientation; 193 [EXUtilities performSynchronouslyOnMainThread:^{ 194 interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; 195 }]; 196 AVCaptureVideoOrientation orientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:interfaceOrientation]; 197 198 EX_WEAKIFY(self); 199 dispatch_async(_sessionQueue, ^{ 200 EX_ENSURE_STRONGIFY(self); 201 202 [self.session beginConfiguration]; 203 204 NSError *error = nil; 205 AVCaptureDevice *captureDevice = [EXBarCodeScannerUtils deviceWithMediaType:AVMediaTypeVideo 206 preferringPosition:self.presetCamera]; 207 AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; 208 209 if (error || captureDeviceInput == nil) { 210 NSString *errorMessage = @"Camera could not be started - "; 211 if (error) { 212 errorMessage = [errorMessage stringByAppendingString:[error description]]; 213 } else { 214 errorMessage = [errorMessage stringByAppendingString:@"there's no captureDeviceInput available"]; 215 } 216 [self onMountingError:@{@"message": errorMessage}]; 217 return; 218 } 219 220 [self.session removeInput:self.videoCaptureDeviceInput]; 221 if ([self.session canAddInput:captureDeviceInput]) { 222 [self.session addInput:captureDeviceInput]; 223 224 self.videoCaptureDeviceInput = captureDeviceInput; 225 [self.previewLayer.connection setVideoOrientation:orientation]; 226 } 227 228 [self.session commitConfiguration]; 229 if (!self.session.isRunning) { 230 [self startSession]; 231 } 232 }); 233} 234 235- (void)startSession 236{ 237#if TARGET_IPHONE_SIMULATOR 238 return; 239#endif 240 if (![self ensurePermissionsGranted]) { 241 return; 242 }; 243 244 EX_WEAKIFY(self); 245 dispatch_async(_sessionQueue, ^{ 246 EX_ENSURE_STRONGIFY(self); 247 248 if (self.presetCamera == AVCaptureDevicePositionUnspecified) { 249 return; 250 } 251 252 [self setRuntimeErrorHandlingObserver: 253 [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification 254 object:self.session 255 queue:nil 256 usingBlock:^(NSNotification *note) { 257 EX_ENSURE_STRONGIFY(self); 258 dispatch_async(self.sessionQueue, ^{ 259 EX_ENSURE_STRONGIFY(self); 260 // Manually restarting the session since it must have been stopped due to an error. 261 [self.session startRunning]; 262 [self onReady]; 263 }); 264 }]]; 265 266 [self.barCodeScanner maybeStartBarCodeScanning]; 267 268 [self.session startRunning]; 269 [self onReady]; 270 }); 271} 272 273- (void)stopSession 274{ 275#if TARGET_IPHONE_SIMULATOR 276 return; 277#endif 278 EX_WEAKIFY(self); 279 dispatch_async(_sessionQueue, ^{ 280 EX_ENSURE_STRONGIFY(self); 281 282 [self.barCodeScanner stopBarCodeScanning]; 283 284 [self.previewLayer removeFromSuperlayer]; 285 [self.session commitConfiguration]; 286 [self.session stopRunning]; 287 for (AVCaptureInput *input in self.session.inputs) { 288 [self.session removeInput:input]; 289 } 290 291 for (AVCaptureOutput *output in self.session.outputs) { 292 [self.session removeOutput:output]; 293 } 294 }); 295} 296 297# pragma mark - BarCode scanner 298 299- (EXBarCodeScanner *)createBarCodeScanner 300{ 301 EXBarCodeScanner *barCodeScanner = [EXBarCodeScanner new]; 302 [barCodeScanner setSession:_session]; 303 [barCodeScanner setSessionQueue:_sessionQueue]; 304 EX_WEAKIFY(self); 305 [barCodeScanner setOnBarCodeScanned:^(NSDictionary *body) { 306 EX_ENSURE_STRONGIFY(self); 307 [self onBarCodeScanned:body]; 308 }]; 309 [barCodeScanner setIsEnabled:true]; 310 return barCodeScanner; 311} 312 313@end 314