@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.
@@ -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
- // 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
+ // Load the native libraries
213
209
  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()) + ")");
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
- 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()) + ")");
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 with listeners
226
+ // Initialize P2P system
238
227
  initializeP2P();
239
- } catch (ClassNotFoundException e) {
240
- Log.e(TAG, "JNIApi class not found: " + e.getMessage());
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
- final long clientPtr = (Long) args[0];
276
- final int state = (Integer) args[1];
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
- if (reactContext != null) {
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
- final long clientPtr = (Long) args[0];
318
- final byte[] data = (byte[]) args[1];
319
- final int length = (Integer) args[2];
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 (reactContext != null) {
323
- reactContext.runOnUiQueueThread(new Runnable() {
324
- @Override
325
- public void run() {
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
- // 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
- }
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
- // 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
- }
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
- private void sendEvent(String eventName, @Nullable WritableMap params) {
436
- if (reactContext != null && reactContext.hasActiveReactInstance()) {
437
- reactContext
438
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bits-innovate/react-native-vstarcam",
3
- "version": "1.0.47",
3
+ "version": "1.0.49",
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: "" }) +