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 <UMPermissionsInterface/UMPermissionsInterface.h>
|
7 | #import <UMCore/UMAppLifecycleService.h>
|
8 | #import <UMCore/UMUtilities.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) UMModuleRegistry *moduleRegistry;
|
21 | @property (nonatomic, weak) id<UMPermissionsInterface> permissionsManager;
|
22 | @property (nonatomic, weak) id<UMAppLifecycleService> lifecycleManager;
|
23 |
|
24 | @property (nonatomic, assign, getter=isSessionPaused) BOOL paused;
|
25 |
|
26 | @property (nonatomic, copy) UMDirectEventBlock onCameraReady;
|
27 | @property (nonatomic, copy) UMDirectEventBlock onMountError;
|
28 | @property (nonatomic, copy) UMDirectEventBlock onBarCodeScanned;
|
29 |
|
30 | @end
|
31 |
|
32 | @implementation EXBarCodeScannerView
|
33 |
|
34 | - (instancetype)initWithModuleRegistry:(UMModuleRegistry *)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(UMAppLifecycleListener)];
|
42 | _permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMPermissionsInterface)];
|
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 | UM_WEAKIFY(self);
|
96 | dispatch_async(_sessionQueue, ^{
|
97 | UM_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 | UM_WEAKIFY(self);
|
135 | dispatch_async(_sessionQueue, ^{
|
136 | UM_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 | UM_WEAKIFY(self);
|
147 | dispatch_async(_sessionQueue, ^{
|
148 | UM_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 | UM_WEAKIFY(self);
|
165 | AVCaptureVideoOrientation videoOrientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:orientation];
|
166 | [UMUtilities performSynchronouslyOnMainThread:^{
|
167 | UM_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 | [UMUtilities performSynchronouslyOnMainThread:^{
|
194 | interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
|
195 | }];
|
196 | AVCaptureVideoOrientation orientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:interfaceOrientation];
|
197 |
|
198 | UM_WEAKIFY(self);
|
199 | dispatch_async(_sessionQueue, ^{
|
200 | UM_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 | #pragma clang diagnostic push
|
238 | #pragma clang diagnostic ignored "-Wunreachable-code"
|
239 | #if TARGET_IPHONE_SIMULATOR
|
240 | return;
|
241 | #endif
|
242 | if (![self ensurePermissionsGranted]) {
|
243 | return;
|
244 | };
|
245 |
|
246 | UM_WEAKIFY(self);
|
247 | dispatch_async(_sessionQueue, ^{
|
248 | UM_ENSURE_STRONGIFY(self);
|
249 |
|
250 | if (self.presetCamera == AVCaptureDevicePositionUnspecified) {
|
251 | return;
|
252 | }
|
253 |
|
254 | [self setRuntimeErrorHandlingObserver:
|
255 | [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification
|
256 | object:self.session
|
257 | queue:nil
|
258 | usingBlock:^(NSNotification *note) {
|
259 | UM_ENSURE_STRONGIFY(self);
|
260 | dispatch_async(self.sessionQueue, ^{
|
261 | UM_ENSURE_STRONGIFY(self);
|
262 | // Manually restarting the session since it must have been stopped due to an error.
|
263 | [self.session startRunning];
|
264 | [self onReady];
|
265 | });
|
266 | }]];
|
267 |
|
268 | [self.barCodeScanner maybeStartBarCodeScanning];
|
269 |
|
270 | [self.session startRunning];
|
271 | [self onReady];
|
272 | });
|
273 | #pragma clang diagnostic pop
|
274 | }
|
275 |
|
276 | - (void)stopSession
|
277 | {
|
278 | #if TARGET_IPHONE_SIMULATOR
|
279 | return;
|
280 | #endif
|
281 | UM_WEAKIFY(self);
|
282 | dispatch_async(_sessionQueue, ^{
|
283 | UM_ENSURE_STRONGIFY(self);
|
284 |
|
285 | [self.barCodeScanner stopBarCodeScanning];
|
286 |
|
287 | [self.previewLayer removeFromSuperlayer];
|
288 | [self.session commitConfiguration];
|
289 | [self.session stopRunning];
|
290 | for (AVCaptureInput *input in self.session.inputs) {
|
291 | [self.session removeInput:input];
|
292 | }
|
293 |
|
294 | for (AVCaptureOutput *output in self.session.outputs) {
|
295 | [self.session removeOutput:output];
|
296 | }
|
297 | });
|
298 | }
|
299 |
|
300 | # pragma mark - BarCode scanner
|
301 |
|
302 | - (EXBarCodeScanner *)createBarCodeScanner
|
303 | {
|
304 | EXBarCodeScanner *barCodeScanner = [EXBarCodeScanner new];
|
305 | [barCodeScanner setSession:_session];
|
306 | [barCodeScanner setSessionQueue:_sessionQueue];
|
307 | UM_WEAKIFY(self);
|
308 | [barCodeScanner setOnBarCodeScanned:^(NSDictionary *body) {
|
309 | UM_ENSURE_STRONGIFY(self);
|
310 | [self onBarCodeScanned:body];
|
311 | }];
|
312 | [barCodeScanner setIsEnabled:true];
|
313 | return barCodeScanner;
|
314 | }
|
315 |
|
316 | @end
|