@bits-innovate/react-native-vstarcam 1.0.47 → 1.0.49
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.
- package/android/src/main/java/com/reactnativevstarcam/VStarCamModule.java +134 -104
- package/android/src/main/java/com/reactnativevstarcam/VStarCamVideoView.java +29 -0
- package/android/src/main/java/com/reactnativevstarcam/VStarCamVideoViewManager.java +8 -1
- package/package.json +1 -1
- package/src/index.ts +18 -0
|
@@ -56,11 +56,22 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
56
56
|
// Counter for unique internal client IDs
|
|
57
57
|
private static final java.util.concurrent.atomic.AtomicInteger nextClientPtr = new java.util.concurrent.atomic.AtomicInteger(1000);
|
|
58
58
|
|
|
59
|
-
// Static proxy references to prevent GC.
|
|
60
59
|
private static Object stateListenerProxy;
|
|
61
60
|
private static Object commandListenerProxy;
|
|
62
61
|
private static Object releaseListenerProxy;
|
|
63
62
|
|
|
63
|
+
// Static context reference for reloads
|
|
64
|
+
private static java.lang.ref.WeakReference<ReactApplicationContext> currentContext;
|
|
65
|
+
|
|
66
|
+
// Static initialization state
|
|
67
|
+
private static boolean isNativeLibraryLoaded = false;
|
|
68
|
+
private static boolean isP2PInitialized = false;
|
|
69
|
+
private static Class<?> jniApiClass = null;
|
|
70
|
+
private static Class<?> stateListenerClass = null;
|
|
71
|
+
private static Class<?> commandListenerClass = null;
|
|
72
|
+
private static Class<?> releaseListenerClass = null;
|
|
73
|
+
private static final Object initLock = new Object();
|
|
74
|
+
|
|
64
75
|
// Helper for conditional debug logging
|
|
65
76
|
private static void logDebug(String message) {
|
|
66
77
|
if (DEBUG_LOGGING) {
|
|
@@ -162,14 +173,6 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
162
173
|
// Client tracking - maps our clientPtr to actual SDK client handle
|
|
163
174
|
// Made static so VStarCamVideoView can access SDK pointers
|
|
164
175
|
private static Map<Integer, ClientInfo> clients = new java.util.concurrent.ConcurrentHashMap<>();
|
|
165
|
-
private boolean isNativeLibraryLoaded = false;
|
|
166
|
-
private boolean isP2PInitialized = false;
|
|
167
|
-
|
|
168
|
-
// JNI API class and listener interfaces
|
|
169
|
-
private Class<?> jniApiClass = null;
|
|
170
|
-
private Class<?> stateListenerClass = null;
|
|
171
|
-
private Class<?> commandListenerClass = null;
|
|
172
|
-
private Class<?> releaseListenerClass = null;
|
|
173
176
|
|
|
174
177
|
private static class ClientInfo {
|
|
175
178
|
String deviceId; // Original device ID (can be virtual UID)
|
|
@@ -198,55 +201,38 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
198
201
|
this.reactContext = reactContext;
|
|
199
202
|
this.executor = Executors.newCachedThreadPool();
|
|
200
203
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Then load the main P2P library
|
|
208
|
-
System.loadLibrary("OKSMARTPPCS");
|
|
209
|
-
isNativeLibraryLoaded = true;
|
|
210
|
-
Log.d(TAG, "OKSMARTPPCS library loaded successfully");
|
|
211
|
-
|
|
212
|
-
// Load JNI API class
|
|
204
|
+
// Update current context for static proxies
|
|
205
|
+
currentContext = new java.lang.ref.WeakReference<>(reactContext);
|
|
206
|
+
|
|
207
|
+
synchronized (initLock) {
|
|
208
|
+
// Load the native libraries
|
|
213
209
|
try {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
commandListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientCommandListener");
|
|
220
|
-
releaseListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientReleaseListener");
|
|
221
|
-
Log.d(TAG, "Listener interfaces loaded");
|
|
222
|
-
|
|
223
|
-
// Log all methods on each listener for debugging
|
|
224
|
-
Log.d(TAG, "ClientStateListener methods:");
|
|
225
|
-
for (Method m : stateListenerClass.getDeclaredMethods()) {
|
|
226
|
-
Log.d(TAG, " -> " + m.getName() + "(" + java.util.Arrays.toString(m.getParameterTypes()) + ")");
|
|
210
|
+
if (!isNativeLibraryLoaded) {
|
|
211
|
+
System.loadLibrary("vp_log");
|
|
212
|
+
System.loadLibrary("OKSMARTPPCS");
|
|
213
|
+
isNativeLibraryLoaded = true;
|
|
214
|
+
Log.d(TAG, "Native libraries loaded successfully");
|
|
227
215
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
216
|
+
|
|
217
|
+
// Load JNI API class
|
|
218
|
+
if (jniApiClass == null) {
|
|
219
|
+
jniApiClass = Class.forName("com.vstarcam.JNIApi");
|
|
220
|
+
stateListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientStateListener");
|
|
221
|
+
commandListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientCommandListener");
|
|
222
|
+
releaseListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientReleaseListener");
|
|
223
|
+
Log.d(TAG, "JNI classes and interfaces loaded");
|
|
235
224
|
}
|
|
236
225
|
|
|
237
|
-
// Initialize P2P system
|
|
226
|
+
// Initialize P2P system
|
|
238
227
|
initializeP2P();
|
|
239
|
-
} catch (
|
|
240
|
-
Log.e(TAG, "
|
|
228
|
+
} catch (Exception e) {
|
|
229
|
+
Log.e(TAG, "Native initialization failed", e);
|
|
241
230
|
}
|
|
242
|
-
} catch (UnsatisfiedLinkError e) {
|
|
243
|
-
Log.e(TAG, "Failed to load native library: " + e.getMessage());
|
|
244
|
-
isNativeLibraryLoaded = false;
|
|
245
231
|
}
|
|
246
232
|
}
|
|
247
233
|
|
|
248
234
|
private void initializeP2P() {
|
|
249
|
-
if (!isNativeLibraryLoaded || jniApiClass == null) {
|
|
235
|
+
if (!VStarCamModule.isNativeLibraryLoaded || VStarCamModule.jniApiClass == null) {
|
|
250
236
|
Log.e(TAG, "Cannot initialize P2P: native library or JNIApi not available");
|
|
251
237
|
return;
|
|
252
238
|
}
|
|
@@ -272,19 +258,18 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
272
258
|
Log.d(TAG, "ClientStateListener." + methodName + " called");
|
|
273
259
|
if (methodName.equals("stateListener")) {
|
|
274
260
|
// args: long clientPtr, int state
|
|
275
|
-
|
|
276
|
-
|
|
261
|
+
long clientPtr = 0;
|
|
262
|
+
int state = 0;
|
|
263
|
+
|
|
264
|
+
if (args != null && args.length >= 2) {
|
|
265
|
+
if (args[0] instanceof Number) clientPtr = ((Number) args[0]).longValue();
|
|
266
|
+
if (args[1] instanceof Number) state = ((Number) args[1]).intValue();
|
|
267
|
+
}
|
|
268
|
+
|
|
277
269
|
Log.d(TAG, "State callback: clientPtr=" + clientPtr + ", state=" + state);
|
|
278
270
|
|
|
279
271
|
// Post to main thread for React Native compatibility
|
|
280
|
-
|
|
281
|
-
reactContext.runOnUiQueueThread(new Runnable() {
|
|
282
|
-
@Override
|
|
283
|
-
public void run() {
|
|
284
|
-
handleStateChange(clientPtr, state);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
}
|
|
272
|
+
VStarCamModule.handleStateChange(clientPtr, state);
|
|
288
273
|
}
|
|
289
274
|
} catch (Exception e) {
|
|
290
275
|
Log.e(TAG, "Error in stateListener callback", e);
|
|
@@ -314,19 +299,20 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
314
299
|
Log.d(TAG, "ClientCommandListener." + methodName + " called");
|
|
315
300
|
if (methodName.equals("commandListener")) {
|
|
316
301
|
// args: long clientPtr, byte[] data, int length (from interface)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Log.d(TAG, "Command callback: clientPtr=" + clientPtr + ", dataLen=" + (data != null ? data.length : 0));
|
|
302
|
+
long clientPtr = 0;
|
|
303
|
+
byte[] data = null;
|
|
304
|
+
int length = 0;
|
|
321
305
|
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
handleCommandReceive(clientPtr, 0, data);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
306
|
+
if (args != null && args.length >= 3) {
|
|
307
|
+
if (args[0] instanceof Number) clientPtr = ((Number) args[0]).longValue();
|
|
308
|
+
if (args[1] instanceof byte[]) data = (byte[]) args[1];
|
|
309
|
+
if (args[2] instanceof Number) length = ((Number) args[2]).intValue();
|
|
329
310
|
}
|
|
311
|
+
|
|
312
|
+
Log.d(TAG, "Command callback: clientPtr=" + clientPtr + ", dataLen=" + (data != null ? data.length : 0));
|
|
313
|
+
|
|
314
|
+
// Post to main thread for React Native compatibility
|
|
315
|
+
VStarCamModule.handleCommandReceive(clientPtr, 0, data);
|
|
330
316
|
}
|
|
331
317
|
} catch (Exception e) {
|
|
332
318
|
Log.e(TAG, "Error in commandListener callback", e);
|
|
@@ -363,53 +349,75 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
363
349
|
);
|
|
364
350
|
|
|
365
351
|
// Call JNIApi.init(stateListener, commandListener, releaseListener)
|
|
366
|
-
Method initMethod = jniApiClass.getMethod("init",
|
|
367
|
-
stateListenerClass, commandListenerClass, releaseListenerClass);
|
|
352
|
+
Method initMethod = VStarCamModule.jniApiClass.getMethod("init",
|
|
353
|
+
VStarCamModule.stateListenerClass, VStarCamModule.commandListenerClass, VStarCamModule.releaseListenerClass);
|
|
368
354
|
initMethod.invoke(null, stateListenerProxy, commandListenerProxy, releaseListenerProxy);
|
|
369
355
|
|
|
370
|
-
isP2PInitialized = true;
|
|
356
|
+
VStarCamModule.isP2PInitialized = true;
|
|
371
357
|
Log.d(TAG, "P2P system initialized successfully!");
|
|
372
358
|
} catch (Exception e) {
|
|
373
359
|
Log.e(TAG, "Failed to initialize P2P: " + e.getMessage(), e);
|
|
374
360
|
}
|
|
375
361
|
}
|
|
376
362
|
|
|
377
|
-
private void handleStateChange(long clientPtr, int state) {
|
|
363
|
+
private static void handleStateChange(long clientPtr, int state) {
|
|
378
364
|
Log.d(TAG, "State change: clientPtr=" + clientPtr + ", state=" + state);
|
|
379
365
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
WritableMap params = Arguments.createMap();
|
|
386
|
-
params.putInt("clientId", ourClientPtr);
|
|
387
|
-
params.putInt("state", state);
|
|
388
|
-
sendEvent("onConnectionStateChanged", params);
|
|
389
|
-
|
|
390
|
-
// Update connected state
|
|
391
|
-
entry.getValue().isConnected = (state == 3); // ONLINE
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
366
|
+
final ReactApplicationContext ctx = currentContext != null ? currentContext.get() : null;
|
|
367
|
+
if (ctx == null || !ctx.hasActiveReactInstance()) {
|
|
368
|
+
Log.w(TAG, "State change ignored: no active React instance");
|
|
369
|
+
return;
|
|
394
370
|
}
|
|
371
|
+
|
|
372
|
+
ctx.runOnUiQueueThread(new Runnable() {
|
|
373
|
+
@Override
|
|
374
|
+
public void run() {
|
|
375
|
+
// Find our client by SDK pointer
|
|
376
|
+
for (Map.Entry<Integer, ClientInfo> entry : clients.entrySet()) {
|
|
377
|
+
if (entry.getValue().sdkClientPtr == clientPtr) {
|
|
378
|
+
int ourClientPtr = entry.getKey();
|
|
379
|
+
|
|
380
|
+
WritableMap params = Arguments.createMap();
|
|
381
|
+
params.putInt("clientId", ourClientPtr);
|
|
382
|
+
params.putInt("state", state);
|
|
383
|
+
sendEvent("onConnectionStateChanged", params);
|
|
384
|
+
|
|
385
|
+
// Update connected state
|
|
386
|
+
entry.getValue().isConnected = (state == 3); // ONLINE
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
});
|
|
395
392
|
}
|
|
396
393
|
|
|
397
|
-
private void handleCommandReceive(long clientPtr, int command, byte[] data) {
|
|
394
|
+
private static void handleCommandReceive(long clientPtr, int command, byte[] data) {
|
|
398
395
|
Log.d(TAG, "Command receive: clientPtr=" + clientPtr + ", command=" + command);
|
|
399
396
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
WritableMap params = Arguments.createMap();
|
|
406
|
-
params.putInt("clientId", ourClientPtr);
|
|
407
|
-
params.putInt("command", command);
|
|
408
|
-
params.putString("data", data != null ? new String(data) : "");
|
|
409
|
-
sendEvent("onCommandReceived", params);
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
397
|
+
final ReactApplicationContext ctx = currentContext != null ? currentContext.get() : null;
|
|
398
|
+
if (ctx == null || !ctx.hasActiveReactInstance()) {
|
|
399
|
+
Log.w(TAG, "Command receive ignored: no active React instance");
|
|
400
|
+
return;
|
|
412
401
|
}
|
|
402
|
+
|
|
403
|
+
ctx.runOnUiQueueThread(new Runnable() {
|
|
404
|
+
@Override
|
|
405
|
+
public void run() {
|
|
406
|
+
// Find our client by SDK pointer
|
|
407
|
+
for (Map.Entry<Integer, ClientInfo> entry : clients.entrySet()) {
|
|
408
|
+
if (entry.getValue().sdkClientPtr == clientPtr) {
|
|
409
|
+
int ourClientPtr = entry.getKey();
|
|
410
|
+
|
|
411
|
+
WritableMap params = Arguments.createMap();
|
|
412
|
+
params.putInt("clientId", ourClientPtr);
|
|
413
|
+
params.putInt("command", command);
|
|
414
|
+
params.putString("data", data != null ? new String(data) : "");
|
|
415
|
+
sendEvent("onCommandReceived", params);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
413
421
|
}
|
|
414
422
|
|
|
415
423
|
@Override
|
|
@@ -432,10 +440,32 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
432
440
|
Log.d(TAG, "removeListeners called with count: " + count);
|
|
433
441
|
}
|
|
434
442
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
443
|
+
@Override
|
|
444
|
+
public void invalidate() {
|
|
445
|
+
Log.d(TAG, "invalidate: Cleaning up " + clients.size() + " clients");
|
|
446
|
+
for (Integer clientId : clients.keySet()) {
|
|
447
|
+
try {
|
|
448
|
+
ClientInfo info = clients.get(clientId);
|
|
449
|
+
if (info != null && info.sdkClientPtr != 0 && jniApiClass != null) {
|
|
450
|
+
Method disconnectMethod = jniApiClass.getMethod("disconnect", long.class);
|
|
451
|
+
disconnectMethod.invoke(null, info.sdkClientPtr);
|
|
452
|
+
|
|
453
|
+
Method destroyMethod = jniApiClass.getMethod("destroy", long.class);
|
|
454
|
+
destroyMethod.invoke(null, info.sdkClientPtr);
|
|
455
|
+
Log.d(TAG, "Cleanup: disconnected and destroyed client " + clientId);
|
|
456
|
+
}
|
|
457
|
+
} catch (Exception e) {
|
|
458
|
+
Log.e(TAG, "Failed to cleanup client " + clientId, e);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
clients.clear();
|
|
462
|
+
super.invalidate();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private static void sendEvent(String eventName, @Nullable WritableMap params) {
|
|
466
|
+
final ReactApplicationContext ctx = currentContext != null ? currentContext.get() : null;
|
|
467
|
+
if (ctx != null && ctx.hasActiveReactInstance()) {
|
|
468
|
+
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
439
469
|
.emit(eventName, params);
|
|
440
470
|
}
|
|
441
471
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.reactnativevstarcam;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
+
import android.graphics.Bitmap;
|
|
4
5
|
import android.graphics.Color;
|
|
5
6
|
import android.graphics.SurfaceTexture;
|
|
6
7
|
import android.util.Log;
|
|
@@ -10,6 +11,8 @@ import android.view.Gravity;
|
|
|
10
11
|
import android.widget.FrameLayout;
|
|
11
12
|
import android.widget.TextView;
|
|
12
13
|
|
|
14
|
+
import java.io.File;
|
|
15
|
+
import java.io.FileOutputStream;
|
|
13
16
|
import java.lang.reflect.Method;
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -279,6 +282,32 @@ public class VStarCamVideoView extends FrameLayout
|
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Capture current frame and save to file.
|
|
287
|
+
*/
|
|
288
|
+
public void captureSnapshot(String path) {
|
|
289
|
+
if (textureView == null) return;
|
|
290
|
+
|
|
291
|
+
Bitmap bitmap = textureView.getBitmap();
|
|
292
|
+
if (bitmap == null) {
|
|
293
|
+
Log.e(TAG, "Failed to capture snapshot: bitmap is null");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
File file = new File(path);
|
|
299
|
+
if (file.exists()) file.delete();
|
|
300
|
+
|
|
301
|
+
FileOutputStream out = new FileOutputStream(file);
|
|
302
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
|
|
303
|
+
out.flush();
|
|
304
|
+
out.close();
|
|
305
|
+
Log.d(TAG, "Snapshot saved to: " + path);
|
|
306
|
+
} catch (Exception e) {
|
|
307
|
+
Log.e(TAG, "Failed to save snapshot", e);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
282
311
|
private void releasePlayer() {
|
|
283
312
|
if (playerPtr == 0) return;
|
|
284
313
|
|
|
@@ -25,6 +25,7 @@ public class VStarCamVideoViewManager extends SimpleViewManager<VStarCamVideoVie
|
|
|
25
25
|
// Command IDs for direct commands from JS
|
|
26
26
|
public static final int COMMAND_START_STREAM = 1;
|
|
27
27
|
public static final int COMMAND_STOP_STREAM = 2;
|
|
28
|
+
public static final int COMMAND_CAPTURE_SNAPSHOT = 3;
|
|
28
29
|
|
|
29
30
|
@NonNull
|
|
30
31
|
@Override
|
|
@@ -76,7 +77,8 @@ public class VStarCamVideoViewManager extends SimpleViewManager<VStarCamVideoVie
|
|
|
76
77
|
public Map<String, Integer> getCommandsMap() {
|
|
77
78
|
return MapBuilder.of(
|
|
78
79
|
"startStream", COMMAND_START_STREAM,
|
|
79
|
-
"stopStream", COMMAND_STOP_STREAM
|
|
80
|
+
"stopStream", COMMAND_STOP_STREAM,
|
|
81
|
+
"captureSnapshot", COMMAND_CAPTURE_SNAPSHOT
|
|
80
82
|
);
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -89,6 +91,11 @@ public class VStarCamVideoViewManager extends SimpleViewManager<VStarCamVideoVie
|
|
|
89
91
|
case "stopStream":
|
|
90
92
|
view.stopStream();
|
|
91
93
|
break;
|
|
94
|
+
case "captureSnapshot":
|
|
95
|
+
if (args != null && args.size() > 0) {
|
|
96
|
+
view.captureSnapshot(args.getString(0));
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
Platform,
|
|
17
17
|
requireNativeComponent,
|
|
18
18
|
ViewStyle,
|
|
19
|
+
UIManager,
|
|
20
|
+
findNodeHandle,
|
|
19
21
|
} from "react-native";
|
|
20
22
|
import React from "react";
|
|
21
23
|
|
|
@@ -62,6 +64,22 @@ if (Platform.OS === "android") {
|
|
|
62
64
|
|
|
63
65
|
export const VStarCamVideoView = VStarCamVideoViewNative || (() => null);
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Capture a snapshot from the video view and save it to a file.
|
|
69
|
+
* @param viewRef Reference to the VStarCamVideoView component
|
|
70
|
+
* @param filePath Absolute path where the JPEG will be saved
|
|
71
|
+
*/
|
|
72
|
+
export function captureSnapshot(viewRef: any, filePath: string) {
|
|
73
|
+
const node = findNodeHandle(viewRef);
|
|
74
|
+
if (node) {
|
|
75
|
+
UIManager.dispatchViewManagerCommand(
|
|
76
|
+
node,
|
|
77
|
+
(UIManager as any).VStarCamVideoView.Commands.captureSnapshot,
|
|
78
|
+
[filePath]
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
65
83
|
const LINKING_ERROR =
|
|
66
84
|
`The package 'react-native-vstarcam' doesn't seem to be linked. Make sure: \n\n` +
|
|
67
85
|
Platform.select({ ios: "- You have run 'pod install'\n", default: "" }) +
|