@bits-innovate/react-native-vstarcam 1.0.47 → 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.
- package/android/src/main/java/com/reactnativevstarcam/VStarCamModule.java +126 -99
- 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,59 +201,52 @@ 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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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()) + ")");
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
231
|
+
// Initialize P2P system
|
|
238
232
|
initializeP2P();
|
|
239
|
-
} catch (
|
|
240
|
-
Log.e(TAG, "
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
}
|
|
@@ -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: "" }) +
|