ilabs-flir 2.2.13 → 2.2.15
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
|
|
@@ -49,6 +53,16 @@ object FlirManager {
|
|
|
49
53
|
fun setTextureCallback(callback: TextureUpdateCallback?) {
|
|
50
54
|
textureCallback = callback
|
|
51
55
|
}
|
|
56
|
+
|
|
57
|
+
interface TemperatureUpdateCallback {
|
|
58
|
+
fun onTemperatureUpdate(temperature: Double)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private var temperatureCallback: TemperatureUpdateCallback? = null
|
|
62
|
+
|
|
63
|
+
fun setTemperatureCallback(callback: TemperatureUpdateCallback?) {
|
|
64
|
+
temperatureCallback = callback
|
|
65
|
+
}
|
|
52
66
|
|
|
53
67
|
fun getLatestBitmap(): Bitmap? = latestBitmap
|
|
54
68
|
|
|
@@ -119,17 +133,23 @@ object FlirManager {
|
|
|
119
133
|
val identity = devices.find { it.deviceId == deviceId }
|
|
120
134
|
|
|
121
135
|
if (identity != null) {
|
|
136
|
+
shouldProcessFrames.set(true)
|
|
122
137
|
sdkManager?.connect(identity)
|
|
123
138
|
} else {
|
|
124
139
|
Log.e(TAG, "Device not found: $deviceId")
|
|
125
140
|
emitError("Device not found: $deviceId")
|
|
126
141
|
}
|
|
127
142
|
}
|
|
143
|
+
|
|
144
|
+
fun switchToDevice(deviceId: String) {
|
|
145
|
+
connectToDevice(deviceId)
|
|
146
|
+
}
|
|
128
147
|
|
|
129
148
|
/**
|
|
130
149
|
* Disconnect
|
|
131
150
|
*/
|
|
132
151
|
fun disconnect() {
|
|
152
|
+
shouldProcessFrames.set(false)
|
|
133
153
|
sdkManager?.disconnect()
|
|
134
154
|
isConnected = false
|
|
135
155
|
isStreaming = false
|
|
@@ -141,6 +161,7 @@ object FlirManager {
|
|
|
141
161
|
* Stop everything
|
|
142
162
|
*/
|
|
143
163
|
fun stop() {
|
|
164
|
+
shouldProcessFrames.set(false)
|
|
144
165
|
disconnect()
|
|
145
166
|
stopDiscovery()
|
|
146
167
|
latestBitmap = null
|
|
@@ -229,14 +250,43 @@ object FlirManager {
|
|
|
229
250
|
}
|
|
230
251
|
|
|
231
252
|
override fun onFrame(bitmap: Bitmap) {
|
|
253
|
+
// IMMEDIATE STOP CHECK
|
|
254
|
+
if (!shouldProcessFrames.get()) {
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// THROTTLE: Limit to ~15 FPS to prevent UI thread flooding
|
|
259
|
+
val now = System.currentTimeMillis()
|
|
260
|
+
if (now - lastEmitMs.get() < 66) { // 66ms ~= 15 FPS
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
lastEmitMs.set(now)
|
|
264
|
+
|
|
232
265
|
latestBitmap = bitmap
|
|
233
|
-
isStreaming = true
|
|
234
266
|
|
|
235
|
-
//
|
|
236
|
-
|
|
267
|
+
// If this is the first frame, notify JS that we are now streaming
|
|
268
|
+
if (!isStreaming) {
|
|
269
|
+
isStreaming = true
|
|
270
|
+
emitDeviceState("streaming")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// NON-BLOCKING TEXTURE UPDATE
|
|
274
|
+
if (textureCallback != null) {
|
|
275
|
+
// We use try-lock to ensure we don't pile up parallel calls,
|
|
276
|
+
// though usually onFrame is serial.
|
|
277
|
+
if (isUpdatingTexture.compareAndSet(false, true)) {
|
|
278
|
+
try {
|
|
279
|
+
textureCallback?.onTextureUpdate(bitmap, 0)
|
|
280
|
+
} catch (e: Exception) {
|
|
281
|
+
Log.e(TAG, "Texture update failed", e)
|
|
282
|
+
} finally {
|
|
283
|
+
isUpdatingTexture.set(false)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
237
287
|
|
|
238
|
-
// Notify RN
|
|
239
|
-
emitFrameToReactNative(bitmap)
|
|
288
|
+
// Notify RN - disabled
|
|
289
|
+
// emitFrameToReactNative(bitmap)
|
|
240
290
|
}
|
|
241
291
|
|
|
242
292
|
override fun onError(message: String) {
|
|
@@ -247,6 +297,8 @@ object FlirManager {
|
|
|
247
297
|
// React Native Emitters
|
|
248
298
|
|
|
249
299
|
private fun emitFrameToReactNative(bitmap: Bitmap) {
|
|
300
|
+
// PERF: Disabled to reduce bridge traffic
|
|
301
|
+
/*
|
|
250
302
|
val now = System.currentTimeMillis()
|
|
251
303
|
if (now - lastEmitMs.get() < minEmitIntervalMs) return
|
|
252
304
|
lastEmitMs.set(now)
|
|
@@ -261,6 +313,7 @@ object FlirManager {
|
|
|
261
313
|
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
262
314
|
.emit("FlirFrameReceived", params)
|
|
263
315
|
} catch (e: Exception) { }
|
|
316
|
+
*/
|
|
264
317
|
}
|
|
265
318
|
|
|
266
319
|
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
|
-
//
|
|
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",
|
|
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,
|
|
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
|
|
151
|
-
// This handles the case where discovery happened before React Native
|
|
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
|
-
|
|
107
|
+
NSArray *devices = ((NSArray * (*)(id, SEL)) objc_msgSend)(
|
|
108
|
+
manager, sel_registerName("getDiscoveredDevices"));
|
|
158
109
|
if (devices && devices.count > 0) {
|
|
159
|
-
NSLog(
|
|
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)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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",
|
|
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
|
|
182
|
-
// or convert this to an instance method
|
|
183
|
-
// [[FlirModule sharedInstance] sendEventWithName:@"FlirBatteryUpdated"
|
|
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",
|
|
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",
|
|
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: %@",
|
|
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",
|
|
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;
|
|
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
|
|
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)
|
|
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 ||
|
|
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)(
|
|
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",
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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(
|
|
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
|
|
package/ios/Flir/src/FlirState.m
CHANGED
|
@@ -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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|