@sbhjt-gr/react-native-webrtc 137.0.5 → 137.0.7

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.
@@ -21,11 +21,13 @@ import com.facebook.react.bridge.WritableArray;
21
21
  import com.facebook.react.bridge.WritableMap;
22
22
  import com.facebook.react.module.annotations.ReactModule;
23
23
  import com.facebook.react.modules.core.DeviceEventManagerModule;
24
+ import com.oney.WebRTCModule.virtualaudio.VirtualAudioController;
24
25
  import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoDecoderFactory;
25
26
  import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory;
26
27
 
27
28
  import org.webrtc.AddIceObserver;
28
29
  import org.webrtc.AudioProcessingFactory;
30
+ import org.webrtc.AudioSource;
29
31
  import org.webrtc.AudioTrack;
30
32
  import org.webrtc.AudioTrackSink;
31
33
  import org.webrtc.CryptoOptions;
@@ -62,6 +64,7 @@ import java.util.HashMap;
62
64
  import java.util.List;
63
65
  import java.util.Map;
64
66
  import java.util.Objects;
67
+ import java.util.UUID;
65
68
  import java.util.concurrent.Callable;
66
69
  import java.util.concurrent.ConcurrentHashMap;
67
70
  import java.util.concurrent.ExecutionException;
@@ -74,6 +77,9 @@ public class WebRTCModule extends ReactContextBaseJavaModule {
74
77
  VideoEncoderFactory mVideoEncoderFactory;
75
78
  VideoDecoderFactory mVideoDecoderFactory;
76
79
  AudioDeviceModule mAudioDeviceModule;
80
+ private PeerConnectionFactory virtualFactory;
81
+ private JavaAudioDeviceModule virtualAdm;
82
+ private VirtualAudioController virtualAudioCtrl;
77
83
 
78
84
  // Need to expose the peer connection codec factories here to get capabilities
79
85
  private final SparseArray<PeerConnectionObserver> mPeerConnectionObservers;
@@ -164,9 +170,48 @@ public class WebRTCModule extends ReactContextBaseJavaModule {
164
170
  return "WebRTCModule";
165
171
  }
166
172
 
173
+ @Override
174
+ public void onCatalystInstanceDestroy() {
175
+ super.onCatalystInstanceDestroy();
176
+ disposeVirtualAudioResources();
177
+ }
178
+
167
179
  public PeerConnectionObserver getPeerConnectionObserver(int id) {
168
180
  return mPeerConnectionObservers.get(id);
169
181
  }
182
+
183
+ private PeerConnectionFactory getVirtualPeerConnectionFactory() {
184
+ if (virtualFactory != null) {
185
+ return virtualFactory;
186
+ }
187
+
188
+ JavaAudioDeviceModule module = JavaAudioDeviceModule.builder(getReactApplicationContext())
189
+ .setUseHardwareAcousticEchoCanceler(false)
190
+ .setUseHardwareNoiseSuppressor(false)
191
+ .createAudioDeviceModule();
192
+ module.setAudioRecordEnabled(false);
193
+ virtualAdm = module;
194
+ virtualAudioCtrl = new VirtualAudioController(module.audioInput);
195
+
196
+ PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder()
197
+ .setAudioDeviceModule(module)
198
+ .setVideoEncoderFactory(mVideoEncoderFactory)
199
+ .setVideoDecoderFactory(mVideoDecoderFactory);
200
+ virtualFactory = builder.createPeerConnectionFactory();
201
+ return virtualFactory;
202
+ }
203
+
204
+ private void disposeVirtualAudioResources() {
205
+ if (virtualFactory != null) {
206
+ virtualFactory.dispose();
207
+ virtualFactory = null;
208
+ }
209
+ if (virtualAdm != null) {
210
+ virtualAdm.release();
211
+ virtualAdm = null;
212
+ }
213
+ virtualAudioCtrl = null;
214
+ }
170
215
  private PeerConnection getPeerConnection(int id) {
171
216
  PeerConnectionObserver pco = mPeerConnectionObservers.get(id);
172
217
  return (pco == null) ? null : pco.getPeerConnection();
@@ -835,6 +880,78 @@ public class WebRTCModule extends ReactContextBaseJavaModule {
835
880
  ThreadUtils.runOnExecutor(() -> callback.invoke(getUserMediaImpl.enumerateDevices()));
836
881
  }
837
882
 
883
+ @ReactMethod
884
+ public void createVirtualAudioTrack(Promise promise) {
885
+ ThreadUtils.runOnExecutor(() -> {
886
+ PeerConnectionFactory factory = getVirtualPeerConnectionFactory();
887
+ if (factory == null || virtualAudioCtrl == null) {
888
+ promise.reject("virtual_factory_error", "Virtual audio unavailable");
889
+ return;
890
+ }
891
+
892
+ MediaConstraints constraints = new MediaConstraints();
893
+ AudioSource source = factory.createAudioSource(constraints);
894
+ AudioTrack audioTrack = factory.createAudioTrack(UUID.randomUUID().toString(), source);
895
+ String streamId = UUID.randomUUID().toString();
896
+ MediaStream mediaStream = factory.createLocalMediaStream(streamId);
897
+ mediaStream.addTrack(audioTrack);
898
+ localStreams.put(streamId, mediaStream);
899
+
900
+ WritableMap trackInfo = Arguments.createMap();
901
+ trackInfo.putBoolean("enabled", audioTrack.enabled());
902
+ trackInfo.putString("id", audioTrack.id());
903
+ trackInfo.putString("kind", audioTrack.kind());
904
+ trackInfo.putString("readyState", "live");
905
+ trackInfo.putBoolean("remote", false);
906
+ WritableMap settings = Arguments.createMap();
907
+ settings.putString("deviceId", "virtual-audio");
908
+ settings.putString("groupId", "");
909
+ trackInfo.putMap("settings", settings);
910
+
911
+ WritableArray tracks = Arguments.createArray();
912
+ tracks.pushMap(trackInfo);
913
+
914
+ WritableMap payload = Arguments.createMap();
915
+ payload.putString("streamId", streamId);
916
+ payload.putArray("tracks", tracks);
917
+ promise.resolve(payload);
918
+
919
+ if (virtualAdm != null) {
920
+ virtualAdm.requestStartRecording();
921
+ }
922
+ });
923
+ }
924
+
925
+ @ReactMethod
926
+ public void pushVirtualAudioSamples(ReadableArray samples, double sampleRate, double channels) {
927
+ ThreadUtils.runOnExecutor(() -> {
928
+ if (virtualAudioCtrl == null || samples == null) {
929
+ return;
930
+ }
931
+
932
+ int rate = (int) sampleRate;
933
+ int channelCount = (int) channels;
934
+ if (rate <= 0 || channelCount <= 0 || samples.size() == 0) {
935
+ return;
936
+ }
937
+
938
+ short[] data = new short[samples.size()];
939
+ for (int i = 0; i < samples.size(); i++) {
940
+ double value = samples.getDouble(i);
941
+ int sample = (int) Math.round(value);
942
+ if (sample > Short.MAX_VALUE) {
943
+ sample = Short.MAX_VALUE;
944
+ } else if (sample < Short.MIN_VALUE) {
945
+ sample = Short.MIN_VALUE;
946
+ }
947
+ data[i] = (short) sample;
948
+ }
949
+
950
+ virtualAudioCtrl.configure(rate, channelCount);
951
+ virtualAudioCtrl.push(data);
952
+ });
953
+ }
954
+
838
955
  @ReactMethod
839
956
  public void mediaStreamCreate(String id) {
840
957
  ThreadUtils.runOnExecutor(() -> {
@@ -0,0 +1,132 @@
1
+ package com.oney.WebRTCModule.virtualaudio;
2
+
3
+ import java.lang.reflect.Field;
4
+ import java.lang.reflect.InvocationTargetException;
5
+ import java.lang.reflect.Method;
6
+ import java.nio.ByteBuffer;
7
+ import java.nio.ByteOrder;
8
+ import java.nio.ShortBuffer;
9
+
10
+ public class VirtualAudioController {
11
+ private final Object audioRecord;
12
+ private final Method nativeDataIsRecorded;
13
+ private final Method nativeCacheDirectBufferAddress;
14
+ private final Field nativeAudioRecordField;
15
+ private final Field byteBufferField;
16
+ private final Field sampleRateField;
17
+ private final Field channelCountField;
18
+ private final Field expectedSampleRateField;
19
+ private final Field expectedChannelCountField;
20
+
21
+ private ByteBuffer buffer;
22
+ private int bufferBytes;
23
+ private int currentSampleRate;
24
+ private int currentChannels;
25
+
26
+ public VirtualAudioController(Object audioRecord) {
27
+ this.audioRecord = audioRecord;
28
+ try {
29
+ Class<?> clazz = audioRecord.getClass();
30
+ nativeDataIsRecorded = clazz.getDeclaredMethod("nativeDataIsRecorded", long.class, int.class, long.class);
31
+ nativeDataIsRecorded.setAccessible(true);
32
+ nativeCacheDirectBufferAddress =
33
+ clazz.getDeclaredMethod("nativeCacheDirectBufferAddress", long.class, ByteBuffer.class);
34
+ nativeCacheDirectBufferAddress.setAccessible(true);
35
+ nativeAudioRecordField = clazz.getDeclaredField("nativeAudioRecord");
36
+ nativeAudioRecordField.setAccessible(true);
37
+ byteBufferField = clazz.getDeclaredField("byteBuffer");
38
+ byteBufferField.setAccessible(true);
39
+ sampleRateField = clazz.getDeclaredField("sampleRate");
40
+ sampleRateField.setAccessible(true);
41
+ channelCountField = clazz.getDeclaredField("channelCount");
42
+ channelCountField.setAccessible(true);
43
+ expectedSampleRateField = clazz.getDeclaredField("expectedSampleRate");
44
+ expectedSampleRateField.setAccessible(true);
45
+ expectedChannelCountField = clazz.getDeclaredField("expectedChannelCount");
46
+ expectedChannelCountField.setAccessible(true);
47
+ buffer = (ByteBuffer) byteBufferField.get(audioRecord);
48
+ bufferBytes = buffer != null ? buffer.capacity() : 0;
49
+ } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) {
50
+ throw new IllegalStateException("Failed to initialize VirtualAudioController", e);
51
+ }
52
+ }
53
+
54
+ public synchronized void configure(int sampleRate, int channels) {
55
+ if (sampleRate <= 0 || channels <= 0) {
56
+ return;
57
+ }
58
+ if (sampleRate == currentSampleRate && channels == currentChannels && buffer != null) {
59
+ return;
60
+ }
61
+ currentSampleRate = sampleRate;
62
+ currentChannels = channels;
63
+ int bytesPerFrame = channels * 2;
64
+ int framesPerBuffer = Math.max(sampleRate / 100, 1);
65
+ int capacity = Math.max(framesPerBuffer * bytesPerFrame, bytesPerFrame);
66
+ buffer = ByteBuffer.allocateDirect(capacity).order(ByteOrder.nativeOrder());
67
+ bufferBytes = capacity;
68
+ try {
69
+ byteBufferField.set(audioRecord, buffer);
70
+ expectedSampleRateField.setInt(audioRecord, sampleRate);
71
+ expectedChannelCountField.setInt(audioRecord, channels);
72
+ sampleRateField.setInt(audioRecord, sampleRate);
73
+ channelCountField.setInt(audioRecord, channels);
74
+ cacheBuffer();
75
+ } catch (IllegalAccessException e) {
76
+ throw new IllegalStateException("Unable to configure VirtualAudioController", e);
77
+ }
78
+ }
79
+
80
+ public synchronized void push(short[] samples) {
81
+ if (samples == null || samples.length == 0 || buffer == null) {
82
+ return;
83
+ }
84
+ long nativePtr = nativePointer();
85
+ if (nativePtr == 0) {
86
+ cacheBuffer();
87
+ nativePtr = nativePointer();
88
+ if (nativePtr == 0) {
89
+ return;
90
+ }
91
+ }
92
+ int offset = 0;
93
+ ShortBuffer shortView = buffer.asShortBuffer();
94
+ int framesCapacity = shortView.capacity();
95
+ while (offset < samples.length) {
96
+ shortView.clear();
97
+ int framesToCopy = Math.min(framesCapacity, samples.length - offset);
98
+ shortView.put(samples, offset, framesToCopy);
99
+ int bytes = framesToCopy * 2;
100
+ invokeNative(nativePtr, bytes);
101
+ offset += framesToCopy;
102
+ }
103
+ }
104
+
105
+ private void cacheBuffer() {
106
+ long nativePtr = nativePointer();
107
+ if (nativePtr == 0 || buffer == null) {
108
+ return;
109
+ }
110
+ try {
111
+ nativeCacheDirectBufferAddress.invoke(audioRecord, nativePtr, buffer);
112
+ } catch (IllegalAccessException | InvocationTargetException e) {
113
+ throw new IllegalStateException("Failed to cache direct buffer", e);
114
+ }
115
+ }
116
+
117
+ private void invokeNative(long nativePtr, int bytes) {
118
+ try {
119
+ nativeDataIsRecorded.invoke(audioRecord, nativePtr, bytes, System.nanoTime());
120
+ } catch (IllegalAccessException | InvocationTargetException e) {
121
+ throw new IllegalStateException("Failed to deliver audio samples", e);
122
+ }
123
+ }
124
+
125
+ private long nativePointer() {
126
+ try {
127
+ return nativeAudioRecordField.getLong(audioRecord);
128
+ } catch (IllegalAccessException e) {
129
+ return 0;
130
+ }
131
+ }
132
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sbhjt-gr/react-native-webrtc",
3
- "version": "137.0.5",
3
+ "version": "137.0.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/sbhjt-gr/react-native-webrtc.git"