ilabs-flir 2.2.13 → 2.2.14

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.
@@ -35,6 +35,10 @@ object FlirManager {
35
35
  private var isStreaming = false
36
36
  private var connectedDeviceId: String? = null
37
37
  private var connectedDeviceName: String? = null
38
+
39
+ // Concurrency control
40
+ private val shouldProcessFrames = java.util.concurrent.atomic.AtomicBoolean(false)
41
+ private val isUpdatingTexture = java.util.concurrent.atomic.AtomicBoolean(false)
38
42
 
39
43
  // Latest bitmap
40
44
  private var latestBitmap: Bitmap? = null
@@ -119,6 +123,7 @@ object FlirManager {
119
123
  val identity = devices.find { it.deviceId == deviceId }
120
124
 
121
125
  if (identity != null) {
126
+ shouldProcessFrames.set(true)
122
127
  sdkManager?.connect(identity)
123
128
  } else {
124
129
  Log.e(TAG, "Device not found: $deviceId")
@@ -130,6 +135,7 @@ object FlirManager {
130
135
  * Disconnect
131
136
  */
132
137
  fun disconnect() {
138
+ shouldProcessFrames.set(false)
133
139
  sdkManager?.disconnect()
134
140
  isConnected = false
135
141
  isStreaming = false
@@ -141,6 +147,7 @@ object FlirManager {
141
147
  * Stop everything
142
148
  */
143
149
  fun stop() {
150
+ shouldProcessFrames.set(false)
144
151
  disconnect()
145
152
  stopDiscovery()
146
153
  latestBitmap = null
@@ -229,14 +236,34 @@ object FlirManager {
229
236
  }
230
237
 
231
238
  override fun onFrame(bitmap: Bitmap) {
239
+ // IMMEDIATE STOP CHECK
240
+ if (!shouldProcessFrames.get()) {
241
+ return
242
+ }
243
+
232
244
  latestBitmap = bitmap
233
- isStreaming = true
234
245
 
235
- // Notify GL callback
236
- textureCallback?.onTextureUpdate(bitmap, 0)
246
+ // If this is the first frame, notify JS that we are now streaming
247
+ if (!isStreaming) {
248
+ isStreaming = true
249
+ emitDeviceState("streaming")
250
+ }
251
+
252
+ // NON-BLOCKING TEXTURE UPDATE (Drop frames if busy)
253
+ if (textureCallback != null) {
254
+ if (isUpdatingTexture.compareAndSet(false, true)) {
255
+ try {
256
+ textureCallback?.onTextureUpdate(bitmap, 0)
257
+ } finally {
258
+ isUpdatingTexture.set(false)
259
+ }
260
+ } else {
261
+ // Log.v(TAG, "Dropping frame - texture update busy")
262
+ }
263
+ }
237
264
 
238
- // Notify RN
239
- emitFrameToReactNative(bitmap)
265
+ // Notify RN - disabled
266
+ // emitFrameToReactNative(bitmap)
240
267
  }
241
268
 
242
269
  override fun onError(message: String) {
@@ -247,6 +274,8 @@ object FlirManager {
247
274
  // React Native Emitters
248
275
 
249
276
  private fun emitFrameToReactNative(bitmap: Bitmap) {
277
+ // PERF: Disabled to reduce bridge traffic
278
+ /*
250
279
  val now = System.currentTimeMillis()
251
280
  if (now - lastEmitMs.get() < minEmitIntervalMs) return
252
281
  lastEmitMs.set(now)
@@ -261,6 +290,7 @@ object FlirManager {
261
290
  ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
262
291
  .emit("FlirFrameReceived", params)
263
292
  } catch (e: Exception) { }
293
+ */
264
294
  }
265
295
 
266
296
  private fun emitDeviceState(state: String) {
@@ -13,6 +13,7 @@
13
13
  #import <React/RCTLog.h>
14
14
  #import <objc/message.h>
15
15
  #import <objc/runtime.h>
16
+ #import <stdatomic.h>
16
17
 
17
18
  // Import Swift-generated header for FlirManagerDelegate protocol
18
19
  #if __has_include("Flir-Swift.h")
@@ -36,61 +37,7 @@ static id flir_manager_shared(void) {
36
37
  return msgSend0((id)cls, sel);
37
38
  }
38
39
 
39
- // Helper for primitives
40
- static double flir_getTemperatureAtPoint(int x, int y) {
41
- id inst = flir_manager_shared();
42
- if (!inst)
43
- return NAN;
44
- SEL sel = sel_registerName("getTemperatureAtPoint:y:");
45
- if (![inst respondsToSelector:sel])
46
- return NAN;
47
- double (*msgSend2)(id, SEL, int, int) =
48
- (double (*)(id, SEL, int, int))objc_msgSend;
49
- return msgSend2(inst, sel, x, y);
50
- }
51
-
52
- static int flir_getBatteryLevel(void) {
53
- id inst = flir_manager_shared();
54
- if (!inst)
55
- return -1;
56
- SEL sel = sel_registerName("getBatteryLevel");
57
- if (![inst respondsToSelector:sel])
58
- return -1;
59
- int (*msgSend0)(id, SEL) = (int (*)(id, SEL))objc_msgSend;
60
- return msgSend0(inst, sel);
61
- }
62
-
63
- static BOOL flir_isBatteryCharging(void) {
64
- id inst = flir_manager_shared();
65
- if (!inst)
66
- return NO;
67
- SEL sel = sel_registerName("isBatteryCharging");
68
- if (![inst respondsToSelector:sel])
69
- return NO;
70
- BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
71
- return msgSend0(inst, sel);
72
- }
73
- static void flir_setPreferSdkRotation(BOOL prefer) {
74
- id inst = flir_manager_shared();
75
- if (!inst)
76
- return;
77
- SEL sel = sel_registerName("setPreferSdkRotation:");
78
- if (![inst respondsToSelector:sel])
79
- return;
80
- void (*msgSend1)(id, SEL, BOOL) = (void (*)(id, SEL, BOOL))objc_msgSend;
81
- msgSend1(inst, sel, prefer);
82
- }
83
-
84
- static BOOL flir_isPreferSdkRotation(void) {
85
- id inst = flir_manager_shared();
86
- if (!inst)
87
- return NO;
88
- SEL sel = sel_registerName("isPreferSdkRotation");
89
- if (![inst respondsToSelector:sel])
90
- return NO;
91
- BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
92
- return msgSend0(inst, sel);
93
- }
40
+ // ... helper primitives skipped in replace ...
94
41
 
95
42
  @interface FlirModule () <FlirManagerDelegate>
96
43
  @property(nonatomic, copy) RCTPromiseResolveBlock connectResolve;
@@ -99,6 +46,7 @@ static BOOL flir_isPreferSdkRotation(void) {
99
46
 
100
47
  @implementation FlirModule {
101
48
  NSInteger _listenerCount;
49
+ atomic_bool _isCapturing;
102
50
  }
103
51
 
104
52
  RCT_EXPORT_MODULE(FlirModule);
@@ -110,6 +58,7 @@ RCT_EXPORT_MODULE(FlirModule);
110
58
  - (instancetype)init {
111
59
  if (self = [super init]) {
112
60
  _listenerCount = 0;
61
+ atomic_store(&_isCapturing, false);
113
62
  // Wire up delegate
114
63
  id manager = flir_manager_shared();
115
64
  if (manager) {
@@ -124,8 +73,8 @@ RCT_EXPORT_MODULE(FlirModule);
124
73
  - (NSArray<NSString *> *)supportedEvents {
125
74
  return @[
126
75
  @"FlirDeviceConnected", @"FlirDeviceDisconnected", @"FlirDevicesFound",
127
- @"FlirFrameReceived", @"FlirFrameBitmapAvailable", @"FlirError", @"FlirStateChanged",
128
- @"FlirBatteryUpdated"
76
+ @"FlirFrameReceived", @"FlirFrameBitmapAvailable", @"FlirError",
77
+ @"FlirStateChanged", @"FlirBatteryUpdated"
129
78
  ];
130
79
  }
131
80
 
@@ -140,23 +89,27 @@ RCT_EXPORT_MODULE(FlirModule);
140
89
 
141
90
  RCT_EXPORT_METHOD(addListener : (NSString *)eventName) {
142
91
  _listenerCount++;
143
- NSLog(@"[FlirModule] addListener: %@ (count: %ld)", eventName, (long)_listenerCount);
144
-
92
+ NSLog(@"[FlirModule] addListener: %@ (count: %ld)", eventName,
93
+ (long)_listenerCount);
94
+
145
95
  // CRITICAL: Call parent to register with RCTEventEmitter's internal tracking
146
96
  // Without this, sendEventWithName will show "no listeners registered" warning
147
97
  // and may not deliver events properly
148
98
  [super addListener:eventName];
149
-
150
- // When FlirDevicesFound listener is added, immediately emit current device list
151
- // This handles the case where discovery happened before React Native mounted
99
+
100
+ // When FlirDevicesFound listener is added, immediately emit current device
101
+ // list This handles the case where discovery happened before React Native
102
+ // mounted
152
103
  if ([eventName isEqualToString:@"FlirDevicesFound"]) {
153
104
  dispatch_async(dispatch_get_main_queue(), ^{
154
105
  id manager = flir_manager_shared();
155
106
  if (manager) {
156
- NSArray *devices = ((NSArray * (*)(id, SEL))
157
- objc_msgSend)(manager, sel_registerName("getDiscoveredDevices"));
107
+ NSArray *devices = ((NSArray * (*)(id, SEL)) objc_msgSend)(
108
+ manager, sel_registerName("getDiscoveredDevices"));
158
109
  if (devices && devices.count > 0) {
159
- NSLog(@"[FlirModule] addListener - re-emitting %lu discovered devices", (unsigned long)devices.count);
110
+ NSLog(
111
+ @"[FlirModule] addListener - re-emitting %lu discovered devices",
112
+ (unsigned long)devices.count);
160
113
  [self onDevicesFound:devices];
161
114
  }
162
115
  }
@@ -166,21 +119,26 @@ RCT_EXPORT_METHOD(addListener : (NSString *)eventName) {
166
119
 
167
120
  RCT_EXPORT_METHOD(removeListeners : (NSInteger)count) {
168
121
  _listenerCount -= count;
169
- if (_listenerCount < 0) _listenerCount = 0;
170
- NSLog(@"[FlirModule] removeListeners: %ld (remaining: %ld)", (long)count, (long)_listenerCount);
171
-
172
- // CRITICAL: Call parent to unregister with RCTEventEmitter's internal tracking
122
+ if (_listenerCount < 0)
123
+ _listenerCount = 0;
124
+ NSLog(@"[FlirModule] removeListeners: %ld (remaining: %ld)", (long)count,
125
+ (long)_listenerCount);
126
+
127
+ // CRITICAL: Call parent to unregister with RCTEventEmitter's internal
128
+ // tracking
173
129
  [super removeListeners:count];
174
130
  }
175
131
 
176
132
  + (void)emitBatteryUpdateWithLevel:(NSInteger)level charging:(BOOL)charging {
177
133
  NSDictionary *payload = @{@"level" : @(level), @"isCharging" : @(charging)};
178
- NSLog(@"[FlirModule] Emitting battery update - level: %ld, charging: %d", (long)level, charging);
179
-
134
+ NSLog(@"[FlirModule] Emitting battery update - level: %ld, charging: %d",
135
+ (long)level, charging);
136
+
180
137
  // Note: This is a class method, so we need to get the module instance
181
- // For now, we'll just log - in production you'd need to get the module instance
182
- // or convert this to an instance method
183
- // [[FlirModule sharedInstance] sendEventWithName:@"FlirBatteryUpdated" body:payload];
138
+ // For now, we'll just log - in production you'd need to get the module
139
+ // instance or convert this to an instance method
140
+ // [[FlirModule sharedInstance] sendEventWithName:@"FlirBatteryUpdated"
141
+ // body:payload];
184
142
  }
185
143
 
186
144
  #pragma mark - Methods
@@ -209,10 +167,12 @@ RCT_EXPORT_METHOD(startDiscovery : (RCTPromiseResolveBlock)
209
167
  id manager = flir_manager_shared();
210
168
  if (manager &&
211
169
  [manager respondsToSelector:sel_registerName("startDiscovery")]) {
212
- NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.startDiscovery", [NSDate date]);
170
+ NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.startDiscovery",
171
+ [NSDate date]);
213
172
  ((void (*)(id, SEL))objc_msgSend)(manager,
214
173
  sel_registerName("startDiscovery"));
215
- NSLog(@"[FlirModule] [%@] ⏱ FlirManager.startDiscovery returned", [NSDate date]);
174
+ NSLog(@"[FlirModule] [%@] ⏱ FlirManager.startDiscovery returned",
175
+ [NSDate date]);
216
176
  }
217
177
  resolve(@(YES));
218
178
  });
@@ -253,23 +213,30 @@ RCT_EXPORT_METHOD(getDiscoveredDevices : (RCTPromiseResolveBlock)
253
213
 
254
214
  RCT_EXPORT_METHOD(connectToDevice : (NSString *)deviceId resolver : (
255
215
  RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
256
- NSLog(@"[FlirModule] [%@] ⏱ RN->connectToDevice called for: %@", [NSDate date], deviceId);
216
+ NSLog(@"[FlirModule] [%@] ⏱ RN->connectToDevice called for: %@",
217
+ [NSDate date], deviceId);
257
218
  dispatch_async(dispatch_get_main_queue(), ^{
258
219
  id manager = flir_manager_shared();
259
220
  if (manager &&
260
221
  [manager respondsToSelector:sel_registerName("connectToDevice:")]) {
261
- NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.connectToDevice", [NSDate date]);
262
-
222
+ NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.connectToDevice",
223
+ [NSDate date]);
224
+
263
225
  // Store callbacks for event-driven updates (but don't block on them)
264
- self.connectResolve = nil; // Don't use promise for blocking
226
+ self.connectResolve = nil; // Don't use promise for blocking
265
227
  self.connectReject = nil;
266
-
228
+
229
+ // Enable capturing
230
+ atomic_store(&_isCapturing, true);
231
+
267
232
  // Initiate connection asynchronously
268
233
  ((void (*)(id, SEL, id))objc_msgSend)(
269
234
  manager, sel_registerName("connectToDevice:"), deviceId);
270
-
271
- NSLog(@"[FlirModule] [%@] ⏱ FlirManager.connectToDevice returned (async started)", [NSDate date]);
272
-
235
+
236
+ NSLog(@"[FlirModule] [%@] ⏱ FlirManager.connectToDevice returned (async "
237
+ @"started)",
238
+ [NSDate date]);
239
+
273
240
  // Resolve immediately - connection status will come via events
274
241
  resolve(@(YES));
275
242
  } else {
@@ -285,6 +252,8 @@ RCT_EXPORT_METHOD(disconnect : (RCTPromiseResolveBlock)
285
252
  id manager = flir_manager_shared();
286
253
  if (manager &&
287
254
  [manager respondsToSelector:sel_registerName("disconnect")]) {
255
+ atomic_store(&_isCapturing, false);
256
+ [[FlirState shared] reset];
288
257
  ((void (*)(id, SEL))objc_msgSend)(manager,
289
258
  sel_registerName("disconnect"));
290
259
  }
@@ -297,6 +266,8 @@ RCT_EXPORT_METHOD(stopFlir : (RCTPromiseResolveBlock)
297
266
  dispatch_async(dispatch_get_main_queue(), ^{
298
267
  id manager = flir_manager_shared();
299
268
  if (manager && [manager respondsToSelector:sel_registerName("stop")]) {
269
+ atomic_store(&_isCapturing, false);
270
+ [[FlirState shared] reset];
300
271
  ((void (*)(id, SEL))objc_msgSend)(manager, sel_registerName("stop"));
301
272
  }
302
273
  resolve(@(YES));
@@ -307,18 +278,21 @@ RCT_EXPORT_METHOD(startEmulator : (NSString *)emulatorType resolver : (
307
278
  RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
308
279
  dispatch_async(dispatch_get_main_queue(), ^{
309
280
  NSLog(@"[FlirModule] startEmulator called for type: %@", emulatorType);
310
-
281
+
311
282
  id manager = flir_manager_shared();
312
283
  if (manager && [manager respondsToSelector:sel_registerName(
313
284
  "startEmulatorWithType:")]) {
314
285
  // Store callbacks for event-driven updates (but don't block on them)
315
286
  self.connectResolve = nil;
316
287
  self.connectReject = nil;
317
-
288
+
289
+ // Enable capturing
290
+ atomic_store(&_isCapturing, true);
291
+
318
292
  // Initiate emulator start asynchronously
319
293
  ((void (*)(id, SEL, id))objc_msgSend)(
320
294
  manager, sel_registerName("startEmulatorWithType:"), emulatorType);
321
-
295
+
322
296
  // Resolve immediately - connection status will come via events
323
297
  resolve(@(YES));
324
298
  } else {
@@ -368,14 +342,18 @@ RCT_EXPORT_METHOD(isEmulator : (RCTPromiseResolveBlock)
368
342
  });
369
343
  }
370
344
 
371
- RCT_EXPORT_METHOD(getLatestFrameBitmap : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
345
+ RCT_EXPORT_METHOD(getLatestFrameBitmap : (RCTPromiseResolveBlock)
346
+ resolve rejecter : (RCTPromiseRejectBlock)reject) {
372
347
  dispatch_async(dispatch_get_main_queue(), ^{
373
348
  id manager = flir_manager_shared();
374
- if (!manager || ![manager respondsToSelector:sel_registerName("latestFrameBitmapBase64")]) {
349
+ if (!manager ||
350
+ ![manager
351
+ respondsToSelector:sel_registerName("latestFrameBitmapBase64")]) {
375
352
  resolve([NSNull null]);
376
353
  return;
377
354
  }
378
- NSDictionary *dict = ((NSDictionary * (*)(id, SEL)) objc_msgSend)(manager, sel_registerName("latestFrameBitmapBase64"));
355
+ NSDictionary *dict = ((NSDictionary * (*)(id, SEL)) objc_msgSend)(
356
+ manager, sel_registerName("latestFrameBitmapBase64"));
379
357
  if (!dict) {
380
358
  resolve([NSNull null]);
381
359
  } else {
@@ -465,15 +443,17 @@ RCT_EXPORT_METHOD(isPreferSdkRotation : (RCTPromiseResolveBlock)
465
443
  objc_msgSend)(d, sel_registerName("toDictionary"))];
466
444
  }
467
445
  }
468
-
469
- NSLog(@"[FlirModule] onDevicesFound - %lu devices, listenerCount: %ld", (unsigned long)arr.count, (long)_listenerCount);
470
-
446
+
447
+ NSLog(@"[FlirModule] onDevicesFound - %lu devices, listenerCount: %ld",
448
+ (unsigned long)arr.count, (long)_listenerCount);
449
+
471
450
  if (_listenerCount > 0) {
472
451
  NSLog(@"[FlirModule] emitting FlirDevicesFound event");
473
- [self sendEventWithName:@"FlirDevicesFound"
452
+ [self sendEventWithName:@"FlirDevicesFound"
474
453
  body:@{@"devices" : arr, @"count" : @(arr.count)}];
475
454
  } else {
476
- NSLog(@"[FlirModule] ⚠️ No listeners registered yet - devices will be re-emitted when listener is added");
455
+ NSLog(@"[FlirModule] ⚠️ No listeners registered yet - devices will be "
456
+ @"re-emitted when listener is added");
477
457
  }
478
458
  }
479
459
 
@@ -485,30 +465,39 @@ RCT_EXPORT_METHOD(isPreferSdkRotation : (RCTPromiseResolveBlock)
485
465
  addEntriesFromDictionary:((NSDictionary * (*)(id, SEL)) objc_msgSend)(
486
466
  device, sel_registerName("toDictionary"))];
487
467
  }
488
-
468
+
489
469
  // Add state info to match Android's FlirDeviceConnected event format
490
470
  [body setObject:@"connected" forKey:@"state"];
491
471
  [body setObject:@(YES) forKey:@"isConnected"];
492
- [body setObject:@(NO) forKey:@"isStreaming"]; // streaming starts after connection
472
+ [body setObject:@(NO)
473
+ forKey:@"isStreaming"]; // streaming starts after connection
493
474
  // isEmulator info should be in device dictionary already from toDictionary
494
475
 
495
- NSLog(@"[FlirModule] onDeviceConnected - emitting FlirDeviceConnected event with state info");
476
+ NSLog(@"[FlirModule] onDeviceConnected - emitting FlirDeviceConnected event "
477
+ @"with state info");
496
478
  [self sendEventWithName:@"FlirDeviceConnected" body:body];
497
479
  }
498
480
 
499
481
  - (void)onDeviceDisconnected {
500
- NSLog(@"[FlirModule] onDeviceDisconnected - emitting FlirDeviceDisconnected event");
482
+ NSLog(@"[FlirModule] onDeviceDisconnected - emitting FlirDeviceDisconnected "
483
+ @"event");
501
484
  [self sendEventWithName:@"FlirDeviceDisconnected" body:@{}];
502
485
  }
503
486
 
504
487
  - (void)onFrameReceived:(UIImage *)image
505
488
  width:(NSInteger)width
506
489
  height:(NSInteger)height {
507
-
508
- // CRITICAL: Update shared state so native preview (FlirPreviewView) receives the texture
490
+
491
+ if (!atomic_load(&_isCapturing)) {
492
+ return;
493
+ }
494
+
495
+ // CRITICAL: Update shared state so native preview (FlirPreviewView) receives
496
+ // the texture
509
497
  [[FlirState shared] updateFrame:image];
510
498
 
511
- // Also emit event for JS consumers (though slow, some might use it)
499
+ // PERF: Commented out to reduce bridge traffic (JS dev not using it)
500
+ /*
512
501
  [self sendEventWithName:@"FlirFrameReceived"
513
502
  body:@{
514
503
  @"width" : @(width),
@@ -516,16 +505,23 @@ RCT_EXPORT_METHOD(isPreferSdkRotation : (RCTPromiseResolveBlock)
516
505
  @"timestamp" :
517
506
  @([[NSDate date] timeIntervalSince1970] * 1000)
518
507
  }];
508
+ */
519
509
  }
520
510
 
521
- - (void)onFrameReceivedRaw:(NSData *)data width:(NSInteger)width height:(NSInteger)height bytesPerRow:(NSInteger)bytesPerRow timestamp:(double)timestamp {
522
- // Emit a lightweight event to notify JS that a raw bitmap is available; raw bytes are available via getLatestFrameBitmap()
523
- [self sendEventWithName:@"FlirFrameBitmapAvailable" body:@{
524
- @"width": @(width),
525
- @"height": @(height),
526
- @"bytesPerRow": @(bytesPerRow),
527
- @"timestamp": @(timestamp)
528
- }];
511
+ - (void)onFrameReceivedRaw:(NSData *)data
512
+ width:(NSInteger)width
513
+ height:(NSInteger)height
514
+ bytesPerRow:(NSInteger)bytesPerRow
515
+ timestamp:(double)timestamp {
516
+ // Emit a lightweight event to notify JS that a raw bitmap is available; raw
517
+ // bytes are available via getLatestFrameBitmap()
518
+ [self sendEventWithName:@"FlirFrameBitmapAvailable"
519
+ body:@{
520
+ @"width" : @(width),
521
+ @"height" : @(height),
522
+ @"bytesPerRow" : @(bytesPerRow),
523
+ @"timestamp" : @(timestamp)
524
+ }];
529
525
  }
530
526
 
531
527
  - (void)onError:(NSString *)message {
@@ -544,7 +540,9 @@ RCT_EXPORT_METHOD(isPreferSdkRotation : (RCTPromiseResolveBlock)
544
540
  @"isStreaming" : @(isStreaming),
545
541
  @"isEmulator" : @(isEmulator)
546
542
  };
547
- NSLog(@"[FlirModule] onStateChanged - state: %@, connected: %d, streaming: %d", state, isConnected, isStreaming);
543
+ NSLog(
544
+ @"[FlirModule] onStateChanged - state: %@, connected: %d, streaming: %d",
545
+ state, isConnected, isStreaming);
548
546
  [self sendEventWithName:@"FlirStateChanged" body:body];
549
547
  }
550
548
 
@@ -7,6 +7,8 @@
7
7
 
8
8
  #import "FlirState.h"
9
9
 
10
+ #import <stdatomic.h>
11
+
10
12
  static FlirState *_sharedState = nil;
11
13
 
12
14
  @implementation FlirState {
@@ -14,6 +16,7 @@ static FlirState *_sharedState = nil;
14
16
  int _imageWidth;
15
17
  int _imageHeight;
16
18
  dispatch_queue_t _accessQueue;
19
+ atomic_bool _isTextureBusy;
17
20
  }
18
21
 
19
22
  + (instancetype)shared {
@@ -33,6 +36,7 @@ static FlirState *_sharedState = nil;
33
36
  _imageHeight = 0;
34
37
  _accessQueue =
35
38
  dispatch_queue_create("com.flir.state.access", DISPATCH_QUEUE_SERIAL);
39
+ atomic_store(&_isTextureBusy, false);
36
40
  }
37
41
  return self;
38
42
  }
@@ -101,11 +105,18 @@ static FlirState *_sharedState = nil;
101
105
 
102
106
  // Invoke texture callback on main thread (for Metal filters, texture unit 7)
103
107
  if (self.onTextureUpdate) {
104
- dispatch_async(dispatch_get_main_queue(), ^{
105
- if (self.onTextureUpdate) {
106
- self.onTextureUpdate(image, 7);
107
- }
108
- });
108
+ bool expected = false;
109
+ if (atomic_compare_exchange_strong(&_isTextureBusy, &expected, true)) {
110
+ dispatch_async(dispatch_get_main_queue(), ^{
111
+ @try {
112
+ if (self.onTextureUpdate) {
113
+ self.onTextureUpdate(image, 7);
114
+ }
115
+ } @finally {
116
+ atomic_store(&_isTextureBusy, false);
117
+ }
118
+ });
119
+ }
109
120
  }
110
121
 
111
122
  // Sample temperature at center point and invoke callback
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.2.13",
3
+ "version": "2.2.14",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",