@bits-innovate/react-native-vstarcam 1.0.46 → 1.0.48

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.
@@ -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,59 +201,52 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
198
201
  this.reactContext = reactContext;
199
202
  this.executor = Executors.newCachedThreadPool();
200
203
 
201
- // Load the native libraries
202
- try {
203
- // Load vp_log first (dependency)
204
- System.loadLibrary("vp_log");
205
- Log.d(TAG, "vp_log library loaded");
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
+ if (isP2PInitialized) {
209
+ Log.d(TAG, "P2P already initialized, skipping...");
210
+ return;
211
+ }
212
+
213
+ // Load the native libraries
213
214
  try {
214
- jniApiClass = Class.forName("com.vstarcam.JNIApi");
215
- Log.d(TAG, "JNIApi class found: " + jniApiClass.getName());
216
-
217
- // Load listener interfaces
218
- stateListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientStateListener");
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()) + ")");
215
+ if (!isNativeLibraryLoaded) {
216
+ System.loadLibrary("vp_log");
217
+ System.loadLibrary("OKSMARTPPCS");
218
+ isNativeLibraryLoaded = true;
219
+ Log.d(TAG, "Native libraries loaded successfully");
227
220
  }
228
- Log.d(TAG, "ClientCommandListener methods:");
229
- for (Method m : commandListenerClass.getDeclaredMethods()) {
230
- Log.d(TAG, " -> " + m.getName() + "(" + java.util.Arrays.toString(m.getParameterTypes()) + ")");
231
- }
232
- Log.d(TAG, "ClientReleaseListener methods:");
233
- for (Method m : releaseListenerClass.getDeclaredMethods()) {
234
- Log.d(TAG, " -> " + m.getName() + "(" + java.util.Arrays.toString(m.getParameterTypes()) + ")");
221
+
222
+ // Load JNI API class
223
+ if (jniApiClass == null) {
224
+ jniApiClass = Class.forName("com.vstarcam.JNIApi");
225
+ stateListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientStateListener");
226
+ commandListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientCommandListener");
227
+ releaseListenerClass = Class.forName("com.vstarcam.app_p2p_api.ClientReleaseListener");
228
+ Log.d(TAG, "JNI classes and interfaces loaded");
235
229
  }
236
230
 
237
- // Initialize P2P system with listeners
231
+ // Initialize P2P system
238
232
  initializeP2P();
239
- } catch (ClassNotFoundException e) {
240
- Log.e(TAG, "JNIApi class not found: " + e.getMessage());
233
+ } catch (Exception e) {
234
+ Log.e(TAG, "Native initialization failed", e);
241
235
  }
242
- } catch (UnsatisfiedLinkError e) {
243
- Log.e(TAG, "Failed to load native library: " + e.getMessage());
244
- isNativeLibraryLoaded = false;
245
236
  }
246
237
  }
247
238
 
248
239
  private void initializeP2P() {
249
- if (!isNativeLibraryLoaded || jniApiClass == null) {
240
+ if (!VStarCamModule.isNativeLibraryLoaded || VStarCamModule.jniApiClass == null) {
250
241
  Log.e(TAG, "Cannot initialize P2P: native library or JNIApi not available");
251
242
  return;
252
243
  }
253
244
 
245
+ if (VStarCamModule.isP2PInitialized) {
246
+ Log.d(TAG, "P2P system already initialized, skipping SDK init calls");
247
+ return;
248
+ }
249
+
254
250
  try {
255
251
  // Create dynamic proxy for ClientStateListener
256
252
  stateListenerProxy = Proxy.newProxyInstance(
@@ -277,14 +273,7 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
277
273
  Log.d(TAG, "State callback: clientPtr=" + clientPtr + ", state=" + state);
278
274
 
279
275
  // Post to main thread for React Native compatibility
280
- if (reactContext != null) {
281
- reactContext.runOnUiQueueThread(new Runnable() {
282
- @Override
283
- public void run() {
284
- handleStateChange(clientPtr, state);
285
- }
286
- });
287
- }
276
+ VStarCamModule.handleStateChange(clientPtr, state);
288
277
  }
289
278
  } catch (Exception e) {
290
279
  Log.e(TAG, "Error in stateListener callback", e);
@@ -319,14 +308,8 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
319
308
  final int length = (Integer) args[2];
320
309
  Log.d(TAG, "Command callback: clientPtr=" + clientPtr + ", dataLen=" + (data != null ? data.length : 0));
321
310
 
322
- if (reactContext != null) {
323
- reactContext.runOnUiQueueThread(new Runnable() {
324
- @Override
325
- public void run() {
326
- handleCommandReceive(clientPtr, 0, data);
327
- }
328
- });
329
- }
311
+ // Post to main thread for React Native compatibility
312
+ VStarCamModule.handleCommandReceive(clientPtr, 0, data);
330
313
  }
331
314
  } catch (Exception e) {
332
315
  Log.e(TAG, "Error in commandListener callback", e);
@@ -363,53 +346,75 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
363
346
  );
364
347
 
365
348
  // Call JNIApi.init(stateListener, commandListener, releaseListener)
366
- Method initMethod = jniApiClass.getMethod("init",
367
- stateListenerClass, commandListenerClass, releaseListenerClass);
349
+ Method initMethod = VStarCamModule.jniApiClass.getMethod("init",
350
+ VStarCamModule.stateListenerClass, VStarCamModule.commandListenerClass, VStarCamModule.releaseListenerClass);
368
351
  initMethod.invoke(null, stateListenerProxy, commandListenerProxy, releaseListenerProxy);
369
352
 
370
- isP2PInitialized = true;
353
+ VStarCamModule.isP2PInitialized = true;
371
354
  Log.d(TAG, "P2P system initialized successfully!");
372
355
  } catch (Exception e) {
373
356
  Log.e(TAG, "Failed to initialize P2P: " + e.getMessage(), e);
374
357
  }
375
358
  }
376
359
 
377
- private void handleStateChange(long clientPtr, int state) {
360
+ private static void handleStateChange(long clientPtr, int state) {
378
361
  Log.d(TAG, "State change: clientPtr=" + clientPtr + ", state=" + state);
379
362
 
380
- // Find our client by SDK pointer
381
- for (Map.Entry<Integer, ClientInfo> entry : clients.entrySet()) {
382
- if (entry.getValue().sdkClientPtr == clientPtr) {
383
- int ourClientPtr = entry.getKey();
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
- }
363
+ final ReactApplicationContext ctx = currentContext != null ? currentContext.get() : null;
364
+ if (ctx == null || !ctx.hasActiveReactInstance()) {
365
+ Log.w(TAG, "State change ignored: no active React instance");
366
+ return;
394
367
  }
368
+
369
+ ctx.runOnUiQueueThread(new Runnable() {
370
+ @Override
371
+ public void run() {
372
+ // Find our client by SDK pointer
373
+ for (Map.Entry<Integer, ClientInfo> entry : clients.entrySet()) {
374
+ if (entry.getValue().sdkClientPtr == clientPtr) {
375
+ int ourClientPtr = entry.getKey();
376
+
377
+ WritableMap params = Arguments.createMap();
378
+ params.putInt("clientId", ourClientPtr);
379
+ params.putInt("state", state);
380
+ sendEvent("onConnectionStateChanged", params);
381
+
382
+ // Update connected state
383
+ entry.getValue().isConnected = (state == 3); // ONLINE
384
+ break;
385
+ }
386
+ }
387
+ }
388
+ });
395
389
  }
396
390
 
397
- private void handleCommandReceive(long clientPtr, int command, byte[] data) {
391
+ private static void handleCommandReceive(long clientPtr, int command, byte[] data) {
398
392
  Log.d(TAG, "Command receive: clientPtr=" + clientPtr + ", command=" + command);
399
393
 
400
- // Find our client by SDK pointer
401
- for (Map.Entry<Integer, ClientInfo> entry : clients.entrySet()) {
402
- if (entry.getValue().sdkClientPtr == clientPtr) {
403
- int ourClientPtr = entry.getKey();
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
- }
394
+ final ReactApplicationContext ctx = currentContext != null ? currentContext.get() : null;
395
+ if (ctx == null || !ctx.hasActiveReactInstance()) {
396
+ Log.w(TAG, "Command receive ignored: no active React instance");
397
+ return;
412
398
  }
399
+
400
+ ctx.runOnUiQueueThread(new Runnable() {
401
+ @Override
402
+ public void run() {
403
+ // Find our client by SDK pointer
404
+ for (Map.Entry<Integer, ClientInfo> entry : clients.entrySet()) {
405
+ if (entry.getValue().sdkClientPtr == clientPtr) {
406
+ int ourClientPtr = entry.getKey();
407
+
408
+ WritableMap params = Arguments.createMap();
409
+ params.putInt("clientId", ourClientPtr);
410
+ params.putInt("command", command);
411
+ params.putString("data", data != null ? new String(data) : "");
412
+ sendEvent("onCommandReceived", params);
413
+ break;
414
+ }
415
+ }
416
+ }
417
+ });
413
418
  }
414
419
 
415
420
  @Override
@@ -432,10 +437,32 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
432
437
  Log.d(TAG, "removeListeners called with count: " + count);
433
438
  }
434
439
 
435
- private void sendEvent(String eventName, @Nullable WritableMap params) {
436
- if (reactContext != null && reactContext.hasActiveReactInstance()) {
437
- reactContext
438
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
440
+ @Override
441
+ public void invalidate() {
442
+ Log.d(TAG, "invalidate: Cleaning up " + clients.size() + " clients");
443
+ for (Integer clientId : clients.keySet()) {
444
+ try {
445
+ ClientInfo info = clients.get(clientId);
446
+ if (info != null && info.sdkClientPtr != 0 && jniApiClass != null) {
447
+ Method disconnectMethod = jniApiClass.getMethod("disconnect", long.class);
448
+ disconnectMethod.invoke(null, info.sdkClientPtr);
449
+
450
+ Method destroyMethod = jniApiClass.getMethod("destroy", long.class);
451
+ destroyMethod.invoke(null, info.sdkClientPtr);
452
+ Log.d(TAG, "Cleanup: disconnected and destroyed client " + clientId);
453
+ }
454
+ } catch (Exception e) {
455
+ Log.e(TAG, "Failed to cleanup client " + clientId, e);
456
+ }
457
+ }
458
+ clients.clear();
459
+ super.invalidate();
460
+ }
461
+
462
+ private static void sendEvent(String eventName, @Nullable WritableMap params) {
463
+ final ReactApplicationContext ctx = currentContext != null ? currentContext.get() : null;
464
+ if (ctx != null && ctx.hasActiveReactInstance()) {
465
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
439
466
  .emit(eventName, params);
440
467
  }
441
468
  }
@@ -978,7 +1005,7 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
978
1005
  @ReactMethod
979
1006
  public void getSdkVersion(Promise promise) {
980
1007
  WritableMap result = Arguments.createMap();
981
- result.putString("version", "1.0.46");
1008
+ result.putString("version", "1.0.47");
982
1009
  result.putBoolean("nativeLoaded", isNativeLibraryLoaded);
983
1010
  result.putString("nativeLib", "OKSMARTPPCS");
984
1011
  result.putBoolean("p2pInitialized", isP2PInitialized);
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bits-innovate/react-native-vstarcam",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
4
4
  "description": "React Native bridge for VStarCam P2P SDK",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
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: "" }) +