@bits-innovate/react-native-vstarcam 1.0.17 → 1.0.19
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/build.gradle +3 -1
- package/android/libs/app_player-5.0.0.aar +0 -0
- package/android/src/main/java/com/reactnativevstarcam/VStarCamModule.java +85 -7
- package/android/src/main/java/com/reactnativevstarcam/VStarCamPackage.java +5 -2
- package/android/src/main/java/com/reactnativevstarcam/VStarCamVideoView.java +354 -0
- package/android/src/main/java/com/reactnativevstarcam/VStarCamVideoViewManager.java +106 -0
- package/package.json +1 -1
- package/src/index.ts +44 -1
- package/android/libs/classes.jar +0 -0
package/android/build.gradle
CHANGED
|
@@ -54,6 +54,8 @@ repositories {
|
|
|
54
54
|
|
|
55
55
|
dependencies {
|
|
56
56
|
implementation "com.facebook.react:react-native:+"
|
|
57
|
-
// AAR for
|
|
57
|
+
// AAR for P2P connectivity
|
|
58
58
|
implementation files('libs/app_p2p_api-5.0.0.aar')
|
|
59
|
+
// AAR for video player/decoder
|
|
60
|
+
implementation files('libs/app_player-5.0.0.aar')
|
|
59
61
|
}
|
|
Binary file
|
|
@@ -140,7 +140,8 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
140
140
|
private final ExecutorService executor;
|
|
141
141
|
|
|
142
142
|
// Client tracking - maps our clientPtr to actual SDK client handle
|
|
143
|
-
|
|
143
|
+
// Made static so VStarCamVideoView can access SDK pointers
|
|
144
|
+
private static Map<Integer, ClientInfo> clients = new HashMap<>();
|
|
144
145
|
private boolean isNativeLibraryLoaded = false;
|
|
145
146
|
private boolean isP2PInitialized = false;
|
|
146
147
|
|
|
@@ -156,8 +157,21 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
156
157
|
long sdkClientPtr = 0; // Actual SDK client pointer (long)
|
|
157
158
|
boolean isConnected = false;
|
|
158
159
|
boolean isLoggedIn = false;
|
|
160
|
+
String username; // Login username for CGI commands
|
|
161
|
+
String password; // Login password for CGI commands
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Get the actual SDK client pointer for a given internal client ID.
|
|
166
|
+
* This is needed by VStarCamVideoView to connect to the player.
|
|
167
|
+
*/
|
|
168
|
+
public static long getSdkClientPtr(int clientPtr) {
|
|
169
|
+
ClientInfo info = clients.get(clientPtr);
|
|
170
|
+
if (info != null) {
|
|
171
|
+
return info.sdkClientPtr;
|
|
172
|
+
}
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
161
175
|
|
|
162
176
|
public VStarCamModule(ReactApplicationContext reactContext) {
|
|
163
177
|
super(reactContext);
|
|
@@ -552,6 +566,8 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
552
566
|
}
|
|
553
567
|
|
|
554
568
|
clientInfo.isLoggedIn = true;
|
|
569
|
+
clientInfo.username = username;
|
|
570
|
+
clientInfo.password = password;
|
|
555
571
|
Log.d(TAG, "JNIApi.login called successfully");
|
|
556
572
|
|
|
557
573
|
promise.resolve(true);
|
|
@@ -680,13 +696,45 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
680
696
|
}
|
|
681
697
|
|
|
682
698
|
/**
|
|
683
|
-
* Start video stream
|
|
699
|
+
* Start video stream by sending livestream.cgi command
|
|
700
|
+
* Resolution: 1=high, 2=general, 4=low, 100=superHD
|
|
684
701
|
*/
|
|
685
702
|
@ReactMethod
|
|
686
703
|
public void startVideoStream(int clientPtr, int streamType, Promise promise) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
704
|
+
Log.d(TAG, "startVideoStream called with type: " + streamType);
|
|
705
|
+
try {
|
|
706
|
+
ClientInfo clientInfo = clients.get(clientPtr);
|
|
707
|
+
if (clientInfo == null || clientInfo.sdkClientPtr == 0) {
|
|
708
|
+
promise.reject("E_NOT_CONNECTED", "Client not connected");
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Send livestream.cgi command to start streaming
|
|
713
|
+
// streamid=10 starts the stream, substream is the resolution
|
|
714
|
+
String cgi = String.format(
|
|
715
|
+
"livestream.cgi?streamid=10&substream=%d&loginuse=%s&loginpas=%s",
|
|
716
|
+
streamType,
|
|
717
|
+
clientInfo.username != null ? clientInfo.username : "admin",
|
|
718
|
+
clientInfo.password != null ? clientInfo.password : ""
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
Log.d(TAG, "Sending livestream start command: " + cgi);
|
|
722
|
+
|
|
723
|
+
Method writeCgiMethod = jniApiClass.getMethod("writeCgi", long.class, String.class, int.class);
|
|
724
|
+
Object result = writeCgiMethod.invoke(null, clientInfo.sdkClientPtr, cgi, 5);
|
|
725
|
+
|
|
726
|
+
Log.d(TAG, "livestream start command sent, result: " + result);
|
|
727
|
+
|
|
728
|
+
WritableMap response = Arguments.createMap();
|
|
729
|
+
response.putBoolean("success", true);
|
|
730
|
+
response.putString("message", "Video stream started");
|
|
731
|
+
response.putInt("resolution", streamType);
|
|
732
|
+
promise.resolve(response);
|
|
733
|
+
|
|
734
|
+
} catch (Exception e) {
|
|
735
|
+
Log.e(TAG, "startVideoStream error", e);
|
|
736
|
+
promise.reject("E_VIDEO_START_FAILED", e.getMessage(), e);
|
|
737
|
+
}
|
|
690
738
|
}
|
|
691
739
|
|
|
692
740
|
/**
|
|
@@ -695,7 +743,37 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
695
743
|
@ReactMethod
|
|
696
744
|
public void stopVideoStream(int clientPtr, Promise promise) {
|
|
697
745
|
Log.d(TAG, "stopVideoStream called");
|
|
698
|
-
|
|
746
|
+
try {
|
|
747
|
+
ClientInfo clientInfo = clients.get(clientPtr);
|
|
748
|
+
if (clientInfo == null || clientInfo.sdkClientPtr == 0) {
|
|
749
|
+
promise.reject("E_NOT_CONNECTED", "Client not connected");
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Send livestream.cgi command to stop streaming
|
|
754
|
+
// streamid=16 stops the stream
|
|
755
|
+
String cgi = String.format(
|
|
756
|
+
"livestream.cgi?streamid=16&substream=0&loginuse=%s&loginpas=%s",
|
|
757
|
+
clientInfo.username != null ? clientInfo.username : "admin",
|
|
758
|
+
clientInfo.password != null ? clientInfo.password : ""
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
Log.d(TAG, "Sending livestream stop command: " + cgi);
|
|
762
|
+
|
|
763
|
+
Method writeCgiMethod = jniApiClass.getMethod("writeCgi", long.class, String.class, int.class);
|
|
764
|
+
Object result = writeCgiMethod.invoke(null, clientInfo.sdkClientPtr, cgi, 5);
|
|
765
|
+
|
|
766
|
+
Log.d(TAG, "livestream stop command sent, result: " + result);
|
|
767
|
+
|
|
768
|
+
WritableMap response = Arguments.createMap();
|
|
769
|
+
response.putBoolean("success", true);
|
|
770
|
+
response.putString("message", "Video stream stopped");
|
|
771
|
+
promise.resolve(response);
|
|
772
|
+
|
|
773
|
+
} catch (Exception e) {
|
|
774
|
+
Log.e(TAG, "stopVideoStream error", e);
|
|
775
|
+
promise.reject("E_VIDEO_STOP_FAILED", e.getMessage(), e);
|
|
776
|
+
}
|
|
699
777
|
}
|
|
700
778
|
|
|
701
779
|
/**
|
|
@@ -757,7 +835,7 @@ public class VStarCamModule extends ReactContextBaseJavaModule {
|
|
|
757
835
|
@ReactMethod
|
|
758
836
|
public void getSdkVersion(Promise promise) {
|
|
759
837
|
WritableMap result = Arguments.createMap();
|
|
760
|
-
result.putString("version", "1.0.
|
|
838
|
+
result.putString("version", "1.0.19");
|
|
761
839
|
result.putBoolean("nativeLoaded", isNativeLibraryLoaded);
|
|
762
840
|
result.putString("nativeLib", "OKSMARTPPCS");
|
|
763
841
|
result.putBoolean("p2pInitialized", isP2PInitialized);
|
|
@@ -8,7 +8,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
|
|
8
8
|
import com.facebook.react.uimanager.ViewManager;
|
|
9
9
|
|
|
10
10
|
import java.util.ArrayList;
|
|
11
|
-
import java.util.
|
|
11
|
+
import java.util.Arrays;
|
|
12
12
|
import java.util.List;
|
|
13
13
|
|
|
14
14
|
public class VStarCamPackage implements ReactPackage {
|
|
@@ -23,6 +23,9 @@ public class VStarCamPackage implements ReactPackage {
|
|
|
23
23
|
@NonNull
|
|
24
24
|
@Override
|
|
25
25
|
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
26
|
-
return
|
|
26
|
+
return Arrays.<ViewManager>asList(
|
|
27
|
+
new VStarCamVideoViewManager()
|
|
28
|
+
);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
31
|
+
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
package com.reactnativevstarcam;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.graphics.Color;
|
|
5
|
+
import android.graphics.SurfaceTexture;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
import android.view.Surface;
|
|
8
|
+
import android.view.TextureView;
|
|
9
|
+
import android.view.Gravity;
|
|
10
|
+
import android.widget.FrameLayout;
|
|
11
|
+
import android.widget.TextView;
|
|
12
|
+
|
|
13
|
+
import java.lang.reflect.Method;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Native video view for VStarCam camera streaming.
|
|
17
|
+
* Uses TextureView for video rendering with the native AppPlayer SDK.
|
|
18
|
+
*/
|
|
19
|
+
public class VStarCamVideoView extends FrameLayout
|
|
20
|
+
implements TextureView.SurfaceTextureListener {
|
|
21
|
+
|
|
22
|
+
private static final String TAG = "VStarCamVideoView";
|
|
23
|
+
|
|
24
|
+
private TextureView textureView;
|
|
25
|
+
private TextView statusView;
|
|
26
|
+
private Surface surface;
|
|
27
|
+
|
|
28
|
+
// Client info
|
|
29
|
+
private long clientPtr = 0;
|
|
30
|
+
private long sdkClientPtr = 0; // Actual SDK pointer from VStarCamModule
|
|
31
|
+
private int resolution = 2;
|
|
32
|
+
|
|
33
|
+
// Player info
|
|
34
|
+
private long playerPtr = 0;
|
|
35
|
+
private boolean isStreaming = false;
|
|
36
|
+
private boolean playerInitialized = false;
|
|
37
|
+
|
|
38
|
+
// Player class (from AAR)
|
|
39
|
+
private Class<?> appPlayerClass;
|
|
40
|
+
|
|
41
|
+
public VStarCamVideoView(Context context) {
|
|
42
|
+
super(context);
|
|
43
|
+
init(context);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private void init(Context context) {
|
|
47
|
+
setBackgroundColor(Color.BLACK);
|
|
48
|
+
|
|
49
|
+
// Create TextureView for video rendering
|
|
50
|
+
textureView = new TextureView(context);
|
|
51
|
+
textureView.setSurfaceTextureListener(this);
|
|
52
|
+
addView(textureView, new LayoutParams(
|
|
53
|
+
LayoutParams.MATCH_PARENT,
|
|
54
|
+
LayoutParams.MATCH_PARENT
|
|
55
|
+
));
|
|
56
|
+
|
|
57
|
+
// Status overlay
|
|
58
|
+
statusView = new TextView(context);
|
|
59
|
+
statusView.setTextColor(Color.WHITE);
|
|
60
|
+
statusView.setTextSize(14);
|
|
61
|
+
statusView.setGravity(Gravity.CENTER);
|
|
62
|
+
statusView.setText("Waiting for connection...");
|
|
63
|
+
LayoutParams statusParams = new LayoutParams(
|
|
64
|
+
LayoutParams.WRAP_CONTENT,
|
|
65
|
+
LayoutParams.WRAP_CONTENT
|
|
66
|
+
);
|
|
67
|
+
statusParams.gravity = Gravity.CENTER;
|
|
68
|
+
addView(statusView, statusParams);
|
|
69
|
+
|
|
70
|
+
// Load player library and find class
|
|
71
|
+
loadPlayerLibrary();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private void loadPlayerLibrary() {
|
|
75
|
+
try {
|
|
76
|
+
// Load the player native lib
|
|
77
|
+
System.loadLibrary("OKSMARTPLAY");
|
|
78
|
+
Log.d(TAG, "OKSMARTPLAY library loaded");
|
|
79
|
+
|
|
80
|
+
// Find AppPlayer class - try multiple possible package names
|
|
81
|
+
String[] classNames = {
|
|
82
|
+
"com.vstarcam.player.AppPlayer",
|
|
83
|
+
"com.vstarcam.AppPlayer",
|
|
84
|
+
"com.oksmartplay.AppPlayer",
|
|
85
|
+
"com.vstarcam.app_player.AppPlayer"
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for (String className : classNames) {
|
|
89
|
+
try {
|
|
90
|
+
appPlayerClass = Class.forName(className);
|
|
91
|
+
Log.d(TAG, "Found player class: " + className);
|
|
92
|
+
|
|
93
|
+
// Log available methods for debugging
|
|
94
|
+
logPlayerMethods();
|
|
95
|
+
break;
|
|
96
|
+
} catch (ClassNotFoundException ignored) {
|
|
97
|
+
Log.d(TAG, "Class not found: " + className);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (appPlayerClass == null) {
|
|
102
|
+
Log.e(TAG, "Could not find AppPlayer class in AAR");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
} catch (UnsatisfiedLinkError e) {
|
|
106
|
+
Log.e(TAG, "Failed to load OKSMARTPLAY: " + e.getMessage());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private void logPlayerMethods() {
|
|
111
|
+
if (appPlayerClass == null) return;
|
|
112
|
+
|
|
113
|
+
Log.d(TAG, "=== AppPlayer Methods ===");
|
|
114
|
+
for (Method m : appPlayerClass.getDeclaredMethods()) {
|
|
115
|
+
StringBuilder params = new StringBuilder();
|
|
116
|
+
for (Class<?> p : m.getParameterTypes()) {
|
|
117
|
+
if (params.length() > 0) params.append(", ");
|
|
118
|
+
params.append(p.getSimpleName());
|
|
119
|
+
}
|
|
120
|
+
Log.d(TAG, " " + m.getName() + "(" + params + ") -> " +
|
|
121
|
+
m.getReturnType().getSimpleName());
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Set the client pointer from VStarCamModule.
|
|
127
|
+
* This is our internal pointer ID, not the SDK pointer.
|
|
128
|
+
*/
|
|
129
|
+
public void setClientPtr(long ptr) {
|
|
130
|
+
this.clientPtr = ptr;
|
|
131
|
+
Log.d(TAG, "Client pointer set: " + ptr);
|
|
132
|
+
|
|
133
|
+
// Lookup the actual SDK pointer from VStarCamModule
|
|
134
|
+
this.sdkClientPtr = VStarCamModule.getSdkClientPtr((int) ptr);
|
|
135
|
+
Log.d(TAG, "SDK client pointer: " + sdkClientPtr);
|
|
136
|
+
|
|
137
|
+
if (sdkClientPtr != 0) {
|
|
138
|
+
statusView.setText("Ready to stream");
|
|
139
|
+
} else {
|
|
140
|
+
statusView.setText("Waiting for connection...");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Set video resolution.
|
|
146
|
+
* 1=high, 2=general, 4=low, 100=superHD
|
|
147
|
+
*/
|
|
148
|
+
public void setResolution(int res) {
|
|
149
|
+
this.resolution = res;
|
|
150
|
+
Log.d(TAG, "Resolution set: " + res);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Start video streaming.
|
|
155
|
+
*/
|
|
156
|
+
public void startStream() {
|
|
157
|
+
if (sdkClientPtr == 0) {
|
|
158
|
+
// Try to get SDK pointer again in case it was set after setClientPtr
|
|
159
|
+
this.sdkClientPtr = VStarCamModule.getSdkClientPtr((int) clientPtr);
|
|
160
|
+
if (sdkClientPtr == 0) {
|
|
161
|
+
Log.e(TAG, "Cannot start: no SDK client pointer");
|
|
162
|
+
statusView.setText("Not connected");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (surface == null) {
|
|
168
|
+
Log.e(TAG, "Cannot start: no surface available");
|
|
169
|
+
statusView.setText("No surface");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (appPlayerClass == null) {
|
|
174
|
+
Log.e(TAG, "Cannot start: player class not found");
|
|
175
|
+
statusView.setText("Player unavailable");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
statusView.setText("Starting stream...");
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// 1. Create player instance if needed
|
|
183
|
+
if (playerPtr == 0) {
|
|
184
|
+
Method createMethod = appPlayerClass.getMethod("create");
|
|
185
|
+
Object result = createMethod.invoke(null);
|
|
186
|
+
playerPtr = (Long) result;
|
|
187
|
+
Log.d(TAG, "Player created: " + playerPtr);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. Set the video source (P2P client)
|
|
191
|
+
try {
|
|
192
|
+
Method setSourceMethod = appPlayerClass.getMethod(
|
|
193
|
+
"setSource", long.class, long.class);
|
|
194
|
+
setSourceMethod.invoke(null, playerPtr, sdkClientPtr);
|
|
195
|
+
Log.d(TAG, "Source set to client: " + sdkClientPtr);
|
|
196
|
+
} catch (NoSuchMethodException e) {
|
|
197
|
+
Log.w(TAG, "setSource method not found, trying alternative...");
|
|
198
|
+
// Try alternative method signatures if the main one doesn't exist
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 3. Set the render surface
|
|
202
|
+
try {
|
|
203
|
+
Method setSurfaceMethod = appPlayerClass.getMethod(
|
|
204
|
+
"setSurface", long.class, Surface.class);
|
|
205
|
+
setSurfaceMethod.invoke(null, playerPtr, surface);
|
|
206
|
+
Log.d(TAG, "Surface set");
|
|
207
|
+
} catch (NoSuchMethodException e) {
|
|
208
|
+
Log.w(TAG, "setSurface method not found, trying alternative...");
|
|
209
|
+
// Try with Object parameter
|
|
210
|
+
try {
|
|
211
|
+
Method setSurfaceMethod = appPlayerClass.getMethod(
|
|
212
|
+
"setSurface", long.class, Object.class);
|
|
213
|
+
setSurfaceMethod.invoke(null, playerPtr, surface);
|
|
214
|
+
Log.d(TAG, "Surface set (via Object)");
|
|
215
|
+
} catch (NoSuchMethodException e2) {
|
|
216
|
+
Log.e(TAG, "No setSurface method found");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 4. Start playback
|
|
221
|
+
try {
|
|
222
|
+
Method startMethod = appPlayerClass.getMethod("start", long.class);
|
|
223
|
+
startMethod.invoke(null, playerPtr);
|
|
224
|
+
Log.d(TAG, "Playback started");
|
|
225
|
+
} catch (NoSuchMethodException e) {
|
|
226
|
+
// Try play() instead of start()
|
|
227
|
+
try {
|
|
228
|
+
Method playMethod = appPlayerClass.getMethod("play", long.class);
|
|
229
|
+
playMethod.invoke(null, playerPtr);
|
|
230
|
+
Log.d(TAG, "Playback started (via play)");
|
|
231
|
+
} catch (NoSuchMethodException e2) {
|
|
232
|
+
Log.e(TAG, "No start/play method found");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
isStreaming = true;
|
|
237
|
+
statusView.setVisibility(GONE);
|
|
238
|
+
|
|
239
|
+
} catch (Exception e) {
|
|
240
|
+
Log.e(TAG, "Failed to start stream", e);
|
|
241
|
+
statusView.setText("Start failed: " + e.getMessage());
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Stop video streaming.
|
|
247
|
+
*/
|
|
248
|
+
public void stopStream() {
|
|
249
|
+
if (!isStreaming || playerPtr == 0) return;
|
|
250
|
+
|
|
251
|
+
Log.d(TAG, "Stopping stream...");
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
try {
|
|
255
|
+
Method stopMethod = appPlayerClass.getMethod("stop", long.class);
|
|
256
|
+
stopMethod.invoke(null, playerPtr);
|
|
257
|
+
Log.d(TAG, "Playback stopped");
|
|
258
|
+
} catch (NoSuchMethodException e) {
|
|
259
|
+
// Try pause() instead
|
|
260
|
+
try {
|
|
261
|
+
Method pauseMethod = appPlayerClass.getMethod("pause", long.class);
|
|
262
|
+
pauseMethod.invoke(null, playerPtr);
|
|
263
|
+
Log.d(TAG, "Playback paused");
|
|
264
|
+
} catch (NoSuchMethodException e2) {
|
|
265
|
+
Log.w(TAG, "No stop/pause method found");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
isStreaming = false;
|
|
270
|
+
statusView.setVisibility(VISIBLE);
|
|
271
|
+
statusView.setText("Stream stopped");
|
|
272
|
+
|
|
273
|
+
} catch (Exception e) {
|
|
274
|
+
Log.e(TAG, "Failed to stop stream", e);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private void releasePlayer() {
|
|
279
|
+
if (playerPtr == 0) return;
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
stopStream();
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
Method releaseMethod = appPlayerClass.getMethod("release", long.class);
|
|
286
|
+
releaseMethod.invoke(null, playerPtr);
|
|
287
|
+
Log.d(TAG, "Player released");
|
|
288
|
+
} catch (NoSuchMethodException e) {
|
|
289
|
+
// Try destroy() instead
|
|
290
|
+
try {
|
|
291
|
+
Method destroyMethod = appPlayerClass.getMethod("destroy", long.class);
|
|
292
|
+
destroyMethod.invoke(null, playerPtr);
|
|
293
|
+
Log.d(TAG, "Player destroyed");
|
|
294
|
+
} catch (NoSuchMethodException e2) {
|
|
295
|
+
Log.w(TAG, "No release/destroy method found");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
playerPtr = 0;
|
|
300
|
+
|
|
301
|
+
} catch (Exception e) {
|
|
302
|
+
Log.e(TAG, "Failed to release player", e);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// TextureView.SurfaceTextureListener
|
|
307
|
+
|
|
308
|
+
@Override
|
|
309
|
+
public void onSurfaceTextureAvailable(
|
|
310
|
+
SurfaceTexture surfaceTexture, int width, int height) {
|
|
311
|
+
Log.d(TAG, "Surface available: " + width + "x" + height);
|
|
312
|
+
this.surface = new Surface(surfaceTexture);
|
|
313
|
+
|
|
314
|
+
// If already supposed to be streaming, try to update surface
|
|
315
|
+
if (isStreaming && playerPtr != 0 && appPlayerClass != null) {
|
|
316
|
+
try {
|
|
317
|
+
Method setSurfaceMethod = appPlayerClass.getMethod(
|
|
318
|
+
"setSurface", long.class, Surface.class);
|
|
319
|
+
setSurfaceMethod.invoke(null, playerPtr, surface);
|
|
320
|
+
Log.d(TAG, "Surface updated on existing player");
|
|
321
|
+
} catch (Exception e) {
|
|
322
|
+
Log.e(TAG, "Failed to update surface", e);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@Override
|
|
328
|
+
public void onSurfaceTextureSizeChanged(
|
|
329
|
+
SurfaceTexture surface, int width, int height) {
|
|
330
|
+
Log.d(TAG, "Surface size changed: " + width + "x" + height);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@Override
|
|
334
|
+
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
|
335
|
+
Log.d(TAG, "Surface destroyed");
|
|
336
|
+
stopStream();
|
|
337
|
+
if (this.surface != null) {
|
|
338
|
+
this.surface.release();
|
|
339
|
+
this.surface = null;
|
|
340
|
+
}
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@Override
|
|
345
|
+
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
|
346
|
+
// Frame rendered - nothing to do
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
@Override
|
|
350
|
+
protected void onDetachedFromWindow() {
|
|
351
|
+
super.onDetachedFromWindow();
|
|
352
|
+
releasePlayer();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package com.reactnativevstarcam;
|
|
2
|
+
|
|
3
|
+
import android.view.View;
|
|
4
|
+
|
|
5
|
+
import androidx.annotation.NonNull;
|
|
6
|
+
import androidx.annotation.Nullable;
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
9
|
+
import com.facebook.react.common.MapBuilder;
|
|
10
|
+
import com.facebook.react.uimanager.SimpleViewManager;
|
|
11
|
+
import com.facebook.react.uimanager.ThemedReactContext;
|
|
12
|
+
import com.facebook.react.uimanager.annotations.ReactProp;
|
|
13
|
+
|
|
14
|
+
import java.util.Map;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* React Native ViewManager for VStarCamVideoView.
|
|
18
|
+
* Exposes the native video view as a React component.
|
|
19
|
+
*/
|
|
20
|
+
public class VStarCamVideoViewManager extends SimpleViewManager<VStarCamVideoView> {
|
|
21
|
+
public static final String REACT_CLASS = "VStarCamVideoView";
|
|
22
|
+
|
|
23
|
+
// Command IDs for direct commands from JS
|
|
24
|
+
public static final int COMMAND_START_STREAM = 1;
|
|
25
|
+
public static final int COMMAND_STOP_STREAM = 2;
|
|
26
|
+
|
|
27
|
+
@NonNull
|
|
28
|
+
@Override
|
|
29
|
+
public String getName() {
|
|
30
|
+
return REACT_CLASS;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@NonNull
|
|
34
|
+
@Override
|
|
35
|
+
protected VStarCamVideoView createViewInstance(@NonNull ThemedReactContext reactContext) {
|
|
36
|
+
return new VStarCamVideoView(reactContext);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set the client pointer for the video stream
|
|
41
|
+
*/
|
|
42
|
+
@ReactProp(name = "clientPtr")
|
|
43
|
+
public void setClientPtr(VStarCamVideoView view, double clientPtr) {
|
|
44
|
+
// React Native passes numbers as doubles, convert to long
|
|
45
|
+
view.setClientPtr((long) clientPtr);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set the video resolution
|
|
50
|
+
* 1 = high, 2 = general/default, 4 = low, 100 = superHD
|
|
51
|
+
*/
|
|
52
|
+
@ReactProp(name = "resolution", defaultInt = 2)
|
|
53
|
+
public void setResolution(VStarCamVideoView view, int resolution) {
|
|
54
|
+
view.setResolution(resolution);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Control streaming via prop
|
|
59
|
+
*/
|
|
60
|
+
@ReactProp(name = "streaming", defaultBoolean = false)
|
|
61
|
+
public void setStreaming(VStarCamVideoView view, boolean streaming) {
|
|
62
|
+
if (streaming) {
|
|
63
|
+
view.startStream();
|
|
64
|
+
} else {
|
|
65
|
+
view.stopStream();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Commands that can be called from JS via dispatchViewManagerCommand
|
|
71
|
+
*/
|
|
72
|
+
@Nullable
|
|
73
|
+
@Override
|
|
74
|
+
public Map<String, Integer> getCommandsMap() {
|
|
75
|
+
return MapBuilder.of(
|
|
76
|
+
"startStream", COMMAND_START_STREAM,
|
|
77
|
+
"stopStream", COMMAND_STOP_STREAM
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Override
|
|
82
|
+
public void receiveCommand(@NonNull VStarCamVideoView view, String commandId, @Nullable ReadableArray args) {
|
|
83
|
+
switch (commandId) {
|
|
84
|
+
case "startStream":
|
|
85
|
+
view.startStream();
|
|
86
|
+
break;
|
|
87
|
+
case "stopStream":
|
|
88
|
+
view.stopStream();
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Events that can be sent to JS
|
|
95
|
+
*/
|
|
96
|
+
@Nullable
|
|
97
|
+
@Override
|
|
98
|
+
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
|
99
|
+
return MapBuilder.<String, Object>builder()
|
|
100
|
+
.put("onStreamStarted", MapBuilder.of("registrationName", "onStreamStarted"))
|
|
101
|
+
.put("onStreamStopped", MapBuilder.of("registrationName", "onStreamStopped"))
|
|
102
|
+
.put("onStreamError", MapBuilder.of("registrationName", "onStreamError"))
|
|
103
|
+
.put("onFrameReceived", MapBuilder.of("registrationName", "onFrameReceived"))
|
|
104
|
+
.build();
|
|
105
|
+
}
|
|
106
|
+
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,7 +10,50 @@
|
|
|
10
10
|
* - Camera settings
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
NativeModules,
|
|
15
|
+
NativeEventEmitter,
|
|
16
|
+
Platform,
|
|
17
|
+
requireNativeComponent,
|
|
18
|
+
ViewStyle,
|
|
19
|
+
} from "react-native";
|
|
20
|
+
import React from "react";
|
|
21
|
+
|
|
22
|
+
// Video View Component
|
|
23
|
+
export interface VStarCamVideoViewProps {
|
|
24
|
+
/** Client pointer from create() call */
|
|
25
|
+
clientPtr: number;
|
|
26
|
+
/** Video resolution: 1=high, 2=general, 4=low, 100=superHD */
|
|
27
|
+
resolution?: 1 | 2 | 4 | 100;
|
|
28
|
+
/** Whether streaming is active */
|
|
29
|
+
streaming?: boolean;
|
|
30
|
+
/** Style for the video view */
|
|
31
|
+
style?: ViewStyle;
|
|
32
|
+
/** Called when stream starts */
|
|
33
|
+
onStreamStarted?: () => void;
|
|
34
|
+
/** Called when stream stops */
|
|
35
|
+
onStreamStopped?: () => void;
|
|
36
|
+
/** Called on stream error */
|
|
37
|
+
onStreamError?: (error: { message: string }) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Native video view component for VStarCam camera streaming.
|
|
42
|
+
*
|
|
43
|
+
* Usage:
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <VStarCamVideoView
|
|
46
|
+
* clientPtr={client.clientPtr}
|
|
47
|
+
* resolution={2}
|
|
48
|
+
* streaming={isPlaying}
|
|
49
|
+
* style={{ width: 300, height: 200 }}
|
|
50
|
+
* />
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export const VStarCamVideoView =
|
|
54
|
+
Platform.OS === "android"
|
|
55
|
+
? requireNativeComponent<VStarCamVideoViewProps>("VStarCamVideoView")
|
|
56
|
+
: () => null; // iOS not implemented yet
|
|
14
57
|
|
|
15
58
|
const LINKING_ERROR =
|
|
16
59
|
`The package 'react-native-vstarcam' doesn't seem to be linked. Make sure: \n\n` +
|
package/android/libs/classes.jar
DELETED
|
Binary file
|