react-native-webrtc-nitro 1.1.0 → 1.2.3
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/3rdparty/output/android/libdatachannel/arm64-v8a/include/rtc/configuration.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/arm64-v8a/include/rtc/rtc.h +9 -1
- package/3rdparty/output/android/libdatachannel/arm64-v8a/include/rtc/rtcpreceivingsession.hpp +26 -1
- package/3rdparty/output/android/libdatachannel/arm64-v8a/include/rtc/rtp.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/arm64-v8a/include/rtc/rtppacketizationconfig.hpp +9 -0
- package/3rdparty/output/android/libdatachannel/arm64-v8a/include/rtc/version.h +3 -3
- package/3rdparty/output/android/libdatachannel/arm64-v8a/lib/libdatachannel.so +0 -0
- package/3rdparty/output/android/libdatachannel/armeabi-v7a/include/rtc/configuration.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/armeabi-v7a/include/rtc/rtc.h +9 -1
- package/3rdparty/output/android/libdatachannel/armeabi-v7a/include/rtc/rtcpreceivingsession.hpp +26 -1
- package/3rdparty/output/android/libdatachannel/armeabi-v7a/include/rtc/rtp.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/armeabi-v7a/include/rtc/rtppacketizationconfig.hpp +9 -0
- package/3rdparty/output/android/libdatachannel/armeabi-v7a/include/rtc/version.h +3 -3
- package/3rdparty/output/android/libdatachannel/armeabi-v7a/lib/libdatachannel.so +0 -0
- package/3rdparty/output/android/libdatachannel/x86/include/rtc/configuration.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/x86/include/rtc/rtc.h +9 -1
- package/3rdparty/output/android/libdatachannel/x86/include/rtc/rtcpreceivingsession.hpp +26 -1
- package/3rdparty/output/android/libdatachannel/x86/include/rtc/rtp.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/x86/include/rtc/rtppacketizationconfig.hpp +9 -0
- package/3rdparty/output/android/libdatachannel/x86/include/rtc/version.h +3 -3
- package/3rdparty/output/android/libdatachannel/x86/lib/libdatachannel.so +0 -0
- package/3rdparty/output/android/libdatachannel/x86_64/include/rtc/configuration.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/x86_64/include/rtc/rtc.h +9 -1
- package/3rdparty/output/android/libdatachannel/x86_64/include/rtc/rtcpreceivingsession.hpp +26 -1
- package/3rdparty/output/android/libdatachannel/x86_64/include/rtc/rtp.hpp +1 -1
- package/3rdparty/output/android/libdatachannel/x86_64/include/rtc/rtppacketizationconfig.hpp +9 -0
- package/3rdparty/output/android/libdatachannel/x86_64/include/rtc/version.h +3 -3
- package/3rdparty/output/android/libdatachannel/x86_64/lib/libdatachannel.so +0 -0
- package/3rdparty/output/ios/ffmpeg.xcframework/ios-arm64/libffmpeg.a +0 -0
- package/3rdparty/output/ios/ffmpeg.xcframework/ios-arm64_x86_64-simulator/libffmpeg.a +0 -0
- package/3rdparty/output/ios/libdatachannel.xcframework/Info.plist +5 -5
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64/Headers/rtc/configuration.hpp +1 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64/Headers/rtc/rtc.h +9 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64/Headers/rtc/rtcpreceivingsession.hpp +26 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64/Headers/rtc/rtp.hpp +1 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64/Headers/rtc/rtppacketizationconfig.hpp +9 -0
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64/Headers/rtc/version.h +3 -3
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64/libdatachannel.a +0 -0
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64_x86_64-simulator/Headers/rtc/configuration.hpp +1 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64_x86_64-simulator/Headers/rtc/rtc.h +9 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64_x86_64-simulator/Headers/rtc/rtcpreceivingsession.hpp +26 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64_x86_64-simulator/Headers/rtc/rtp.hpp +1 -1
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64_x86_64-simulator/Headers/rtc/rtppacketizationconfig.hpp +9 -0
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64_x86_64-simulator/Headers/rtc/version.h +3 -3
- package/3rdparty/output/ios/libdatachannel.xcframework/ios-arm64_x86_64-simulator/libdatachannel.a +0 -0
- package/README.md +563 -13
- package/android/src/main/java/com/webrtc/HybridCamera.kt +0 -1
- package/android/src/main/java/com/webrtc/HybridMicrophone.kt +0 -1
- package/android/src/main/java/com/webrtc/HybridPermissions.kt +95 -0
- package/cpp/Hybrid/HybridMediaDevices.cpp +23 -19
- package/ios/HybridCamera.swift +1 -3
- package/ios/HybridMicrophone.swift +1 -3
- package/ios/HybridPermissions.swift +63 -0
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/Permissions.nitro.js +9 -0
- package/lib/commonjs/specs/Permissions.nitro.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/Permissions.nitro.js +6 -0
- package/lib/module/specs/Permissions.nitro.js.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/Permissions.nitro.d.ts +17 -0
- package/lib/typescript/src/specs/Permissions.nitro.d.ts.map +1 -0
- package/nitro.json +4 -0
- package/nitrogen/generated/android/Webrtc+autolinking.cmake +2 -0
- package/nitrogen/generated/android/WebrtcOnLoad.cpp +10 -0
- package/nitrogen/generated/android/c++/JHybridPermissionsSpec.cpp +91 -0
- package/nitrogen/generated/android/c++/JHybridPermissionsSpec.hpp +66 -0
- package/nitrogen/generated/android/c++/JPermissionDescriptor.hpp +58 -0
- package/nitrogen/generated/android/c++/JPermissionName.hpp +59 -0
- package/nitrogen/generated/android/c++/JPermissionState.hpp +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/webrtc/HybridPermissionsSpec.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/webrtc/PermissionDescriptor.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/webrtc/PermissionName.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/webrtc/PermissionState.kt +22 -0
- package/nitrogen/generated/ios/Webrtc-Swift-Cxx-Bridge.cpp +25 -0
- package/nitrogen/generated/ios/Webrtc-Swift-Cxx-Bridge.hpp +63 -0
- package/nitrogen/generated/ios/Webrtc-Swift-Cxx-Umbrella.hpp +14 -0
- package/nitrogen/generated/ios/WebrtcAutolinking.mm +8 -0
- package/nitrogen/generated/ios/WebrtcAutolinking.swift +15 -0
- package/nitrogen/generated/ios/c++/HybridPermissionsSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridPermissionsSpecSwift.hpp +92 -0
- package/nitrogen/generated/ios/swift/Func_void_PermissionState.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridPermissionsSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridPermissionsSpec_cxx.swift +157 -0
- package/nitrogen/generated/ios/swift/PermissionDescriptor.swift +36 -0
- package/nitrogen/generated/ios/swift/PermissionName.swift +40 -0
- package/nitrogen/generated/ios/swift/PermissionState.swift +44 -0
- package/nitrogen/generated/shared/c++/HybridPermissionsSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridPermissionsSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/PermissionDescriptor.hpp +76 -0
- package/nitrogen/generated/shared/c++/PermissionName.hpp +76 -0
- package/nitrogen/generated/shared/c++/PermissionState.hpp +80 -0
- package/package.json +4 -5
- package/src/index.ts +1 -0
- package/src/specs/Permissions.nitro.ts +22 -0
- package/android/src/main/java/com/webrtc/Permission.kt +0 -58
- package/ios/Permission.swift +0 -26
package/README.md
CHANGED
|
@@ -1,29 +1,579 @@
|
|
|
1
1
|
# react-native-webrtc-nitro
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/react-native-webrtc)
|
|
3
|
+
[](https://www.npmjs.com/package/react-native-webrtc-nitro)
|
|
6
4
|
[](https://www.npmjs.com/package/react-native-webrtc-nitro)
|
|
7
|
-
[](https://github.com/
|
|
5
|
+
[](https://github.com/SingTown/react-native-webrtc-nitro/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
A high-performance WebRTC library built on [Nitro Modules](https://github.com/mrousavy/nitro), providing powerful real-time audio and video communication capabilities for React Native applications.
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
- 🚀 **High Performance**: Built on Nitro Modules for native-level performance
|
|
12
|
+
- 🎥 **Complete WebRTC API**: Support for audio/video calls, data channels, and more
|
|
13
|
+
- 📹 **Media Recording**: Built-in MediaRecorder support for recording audio/video streams
|
|
14
|
+
- 🎬 **FFmpeg Integration**: Powerful audio/video encoding, decoding, transcoding, and processing capabilities
|
|
15
|
+
- 🎞️ **H.264/H.265 Support**: Hardware-accelerated H.264 and H.265 (HEVC) encoding and decoding
|
|
16
|
+
- 🎵 **Opus Audio Codec**: High-quality Opus audio encoding and decoding for efficient audio streaming
|
|
17
|
+
- 📱 **Cross-Platform**: Perfect support for iOS and Android
|
|
18
|
+
- 🔧 **TypeScript**: Full type definitions
|
|
19
|
+
- 🎨 **Nitro Views**: High-performance video rendering views
|
|
10
20
|
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
## 📋 Requirements
|
|
22
|
+
|
|
23
|
+
- React Native >= 0.76.0
|
|
24
|
+
- Node.js >= 18.0.0
|
|
25
|
+
- iOS >= 13.0
|
|
26
|
+
- Android API >= 24
|
|
13
27
|
|
|
14
28
|
> [!IMPORTANT]
|
|
15
|
-
> To
|
|
29
|
+
> To use `Nitro Views` features, React Native >= 0.78.0 is required
|
|
30
|
+
|
|
31
|
+
## 📦 Installation
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
Using pnpm:
|
|
18
34
|
|
|
19
35
|
```bash
|
|
20
36
|
pnpm add react-native-webrtc-nitro react-native-nitro-modules
|
|
21
37
|
```
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
Using npm:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install react-native-webrtc-nitro react-native-nitro-modules
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Using yarn:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
yarn add react-native-webrtc-nitro react-native-nitro-modules
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### iOS Configuration
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd ios && pod install
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Add permission descriptions to `Info.plist`:
|
|
58
|
+
|
|
59
|
+
```xml
|
|
60
|
+
<key>NSCameraUsageDescription</key>
|
|
61
|
+
<string>Camera access is required for video calls</string>
|
|
62
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
63
|
+
<string>Microphone access is required for audio calls</string>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Android Configuration
|
|
67
|
+
|
|
68
|
+
Add permissions to `AndroidManifest.xml`:
|
|
69
|
+
|
|
70
|
+
```xml
|
|
71
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
72
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
73
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🚀 Quick Start
|
|
77
|
+
|
|
78
|
+
### Create Offer (Caller Side)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import {
|
|
82
|
+
MediaDevices,
|
|
83
|
+
RTCPeerConnection,
|
|
84
|
+
Permissions
|
|
85
|
+
} from 'react-native-webrtc-nitro';
|
|
86
|
+
|
|
87
|
+
// 1. Check and request permissions
|
|
88
|
+
let cameraPermission = await Permissions.query({ name: 'camera' });
|
|
89
|
+
if (cameraPermission !== 'granted') {
|
|
90
|
+
cameraPermission = await Permissions.request({ name: 'camera' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let microphonePermission = await Permissions.query({ name: 'microphone' });
|
|
94
|
+
if (microphonePermission !== 'granted') {
|
|
95
|
+
microphonePermission = await Permissions.request({ name: 'microphone' });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (cameraPermission !== 'granted' || microphonePermission !== 'granted') {
|
|
99
|
+
console.error('Camera and microphone permissions are required');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. Get local media stream
|
|
104
|
+
const localStream = await MediaDevices.getUserMedia({
|
|
105
|
+
audio: true,
|
|
106
|
+
video: true
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// 3. Create RTCPeerConnection
|
|
110
|
+
const peerConnection = new RTCPeerConnection({
|
|
111
|
+
iceServers: [
|
|
112
|
+
{ urls: 'stun:stun.l.google.com:19302' }
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 4. Add local stream to connection
|
|
117
|
+
localStream.getTracks().forEach(track => {
|
|
118
|
+
peerConnection.addTransceiver(track, {
|
|
119
|
+
direction: 'sendrecv',
|
|
120
|
+
streams: [localStream]
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// 5. Handle ICE candidates
|
|
125
|
+
peerConnection.onicecandidate = (event) => {
|
|
126
|
+
if (event.candidate) {
|
|
127
|
+
// Send ICE candidate to remote peer via signaling server
|
|
128
|
+
signalingServer.send({
|
|
129
|
+
type: 'candidate',
|
|
130
|
+
candidate: event.candidate
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// 6. Handle remote stream
|
|
136
|
+
peerConnection.ontrack = (event) => {
|
|
137
|
+
const remoteStream = event.streams[0];
|
|
138
|
+
setRemoteStream(remoteStream);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// 7. Create and send offer
|
|
142
|
+
const offer = await peerConnection.createOffer();
|
|
143
|
+
await peerConnection.setLocalDescription(offer);
|
|
144
|
+
|
|
145
|
+
// Send offer to remote peer via signaling server
|
|
146
|
+
signalingServer.send({
|
|
147
|
+
type: 'offer',
|
|
148
|
+
sdp: offer.sdp
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// 8. Receive answer from remote peer
|
|
152
|
+
signalingServer.on('answer', async (answer) => {
|
|
153
|
+
await peerConnection.setRemoteDescription({
|
|
154
|
+
type: 'answer',
|
|
155
|
+
sdp: answer.sdp
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 9. Receive ICE candidates from remote peer
|
|
160
|
+
signalingServer.on('candidate', async (candidate) => {
|
|
161
|
+
await peerConnection.addIceCandidate({
|
|
162
|
+
candidate: candidate.candidate,
|
|
163
|
+
sdpMid: candidate.sdpMid
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Create Answer (Callee Side)
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import {
|
|
172
|
+
MediaDevices,
|
|
173
|
+
RTCPeerConnection,
|
|
174
|
+
Permissions
|
|
175
|
+
} from 'react-native-webrtc-nitro';
|
|
176
|
+
|
|
177
|
+
// 1. Check and request permissions
|
|
178
|
+
let cameraPermission = await Permissions.query({ name: 'camera' });
|
|
179
|
+
if (cameraPermission !== 'granted') {
|
|
180
|
+
cameraPermission = await Permissions.request({ name: 'camera' });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let microphonePermission = await Permissions.query({ name: 'microphone' });
|
|
184
|
+
if (microphonePermission !== 'granted') {
|
|
185
|
+
microphonePermission = await Permissions.request({ name: 'microphone' });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (cameraPermission !== 'granted' || microphonePermission !== 'granted') {
|
|
189
|
+
console.error('Camera and microphone permissions are required');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 2. Get local media stream
|
|
194
|
+
const localStream = await MediaDevices.getUserMedia({
|
|
195
|
+
audio: true,
|
|
196
|
+
video: true
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// 3. Create RTCPeerConnection
|
|
200
|
+
const peerConnection = new RTCPeerConnection({
|
|
201
|
+
iceServers: [
|
|
202
|
+
{ urls: 'stun:stun.l.google.com:19302' }
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// 4. Add local stream to connection
|
|
207
|
+
localStream.getTracks().forEach(track => {
|
|
208
|
+
peerConnection.addTransceiver(track, {
|
|
209
|
+
direction: 'sendrecv',
|
|
210
|
+
streams: [localStream]
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// 5. Handle ICE candidates
|
|
215
|
+
peerConnection.onicecandidate = (event) => {
|
|
216
|
+
if (event.candidate) {
|
|
217
|
+
// Send ICE candidate to remote peer via signaling server
|
|
218
|
+
signalingServer.send({
|
|
219
|
+
type: 'candidate',
|
|
220
|
+
candidate: event.candidate
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// 6. Handle remote stream
|
|
226
|
+
peerConnection.ontrack = (event) => {
|
|
227
|
+
const remoteStream = event.streams[0];
|
|
228
|
+
setRemoteStream(remoteStream);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// 7. Receive offer from remote peer
|
|
232
|
+
signalingServer.on('offer', async (offer) => {
|
|
233
|
+
// Set remote description
|
|
234
|
+
await peerConnection.setRemoteDescription({
|
|
235
|
+
type: 'offer',
|
|
236
|
+
sdp: offer.sdp
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Create and send answer
|
|
240
|
+
const answer = await peerConnection.createAnswer();
|
|
241
|
+
await peerConnection.setLocalDescription(answer);
|
|
242
|
+
|
|
243
|
+
// Send answer back to caller via signaling server
|
|
244
|
+
signalingServer.send({
|
|
245
|
+
type: 'answer',
|
|
246
|
+
sdp: answer.sdp
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// 8. Receive ICE candidates from remote peer
|
|
251
|
+
signalingServer.on('candidate', async (candidate) => {
|
|
252
|
+
await peerConnection.addIceCandidate({
|
|
253
|
+
candidate: candidate.candidate,
|
|
254
|
+
sdpMid: candidate.sdpMid
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Video Rendering
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { WebrtcView } from 'react-native-webrtc-nitro';
|
|
263
|
+
|
|
264
|
+
function VideoCall() {
|
|
265
|
+
const [localStream, setLocalStream] = useState<MediaStream | null>(null);
|
|
266
|
+
const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null);
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<View style={styles.container}>
|
|
270
|
+
{/* Local video */}
|
|
271
|
+
<WebrtcView
|
|
272
|
+
style={styles.localVideo}
|
|
273
|
+
stream={localStream}
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
{/* Remote video */}
|
|
277
|
+
<WebrtcView
|
|
278
|
+
style={styles.remoteVideo}
|
|
279
|
+
stream={remoteStream}
|
|
280
|
+
/>
|
|
281
|
+
</View>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Media Recording
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { MediaRecorder } from 'react-native-webrtc-nitro';
|
|
290
|
+
import RNFS from 'react-native-fs';
|
|
291
|
+
|
|
292
|
+
// Create recorder with media stream
|
|
293
|
+
const recorder = new MediaRecorder(mediaStream);
|
|
294
|
+
|
|
295
|
+
// Take a photo from the stream
|
|
296
|
+
const photoPath = `${RNFS.DocumentDirectoryPath}/photo.jpg`;
|
|
297
|
+
await recorder.takePhoto(photoPath);
|
|
298
|
+
console.log('Photo saved to:', photoPath);
|
|
299
|
+
|
|
300
|
+
// Start video recording
|
|
301
|
+
const videoPath = `${RNFS.DocumentDirectoryPath}/video.mp4`;
|
|
302
|
+
recorder.startRecording(videoPath);
|
|
303
|
+
|
|
304
|
+
// Stop recording after some time
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
recorder.stopRecording();
|
|
307
|
+
console.log('Video saved to:', videoPath);
|
|
308
|
+
}, 10000);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## 📚 API Documentation
|
|
312
|
+
|
|
313
|
+
### Permissions
|
|
314
|
+
|
|
315
|
+
Permission management module
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// Query permission status
|
|
319
|
+
const cameraStatus = await Permissions.query({ name: 'camera' });
|
|
320
|
+
const microphoneStatus = await Permissions.query({ name: 'microphone' });
|
|
321
|
+
// Returns: 'granted' | 'denied' | 'prompt'
|
|
322
|
+
|
|
323
|
+
// Request permissions
|
|
324
|
+
const cameraPermission = await Permissions.request({ name: 'camera' });
|
|
325
|
+
const microphonePermission = await Permissions.request({ name: 'microphone' });
|
|
326
|
+
// Returns: 'granted' | 'denied' | 'prompt'
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### MediaDevices
|
|
330
|
+
|
|
331
|
+
Media device access module
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// Get user media
|
|
335
|
+
const stream = await MediaDevices.getUserMedia({
|
|
336
|
+
audio: true,
|
|
337
|
+
video: true
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Get audio only
|
|
341
|
+
const audioStream = await MediaDevices.getUserMedia({
|
|
342
|
+
audio: true,
|
|
343
|
+
video: false
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Get video only
|
|
347
|
+
const videoStream = await MediaDevices.getUserMedia({
|
|
348
|
+
audio: false,
|
|
349
|
+
video: true
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Get mock media for testing
|
|
353
|
+
const mockStream = await MediaDevices.getMockMedia({
|
|
354
|
+
audio: true,
|
|
355
|
+
video: true
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### MediaStream
|
|
360
|
+
|
|
361
|
+
Media stream management
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// Get stream ID
|
|
365
|
+
const streamId = stream.id;
|
|
366
|
+
|
|
367
|
+
// Get all tracks
|
|
368
|
+
const allTracks = stream.getTracks();
|
|
369
|
+
|
|
370
|
+
// Get audio tracks
|
|
371
|
+
const audioTracks = stream.getAudioTracks();
|
|
372
|
+
|
|
373
|
+
// Get video tracks
|
|
374
|
+
const videoTracks = stream.getVideoTracks();
|
|
375
|
+
|
|
376
|
+
// Add track to stream
|
|
377
|
+
stream.addTrack(track);
|
|
378
|
+
|
|
379
|
+
// Remove track from stream
|
|
380
|
+
stream.removeTrack(track);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### MediaStreamTrack
|
|
384
|
+
|
|
385
|
+
Media track operations
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// Get track ID
|
|
389
|
+
const trackId = track.id;
|
|
390
|
+
|
|
391
|
+
// Get track kind ('audio' or 'video')
|
|
392
|
+
const trackKind = track.kind;
|
|
393
|
+
|
|
394
|
+
// Get track state ('live' or 'ended')
|
|
395
|
+
const trackState = track.readyState;
|
|
396
|
+
|
|
397
|
+
// Enable/disable track
|
|
398
|
+
track.enabled = true;
|
|
399
|
+
track.enabled = false;
|
|
400
|
+
|
|
401
|
+
// Stop track
|
|
402
|
+
track.stop();
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### RTCPeerConnection
|
|
406
|
+
|
|
407
|
+
Peer-to-peer connection
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Create connection
|
|
411
|
+
const pc = new RTCPeerConnection(config);
|
|
412
|
+
|
|
413
|
+
// Add transceiver with track
|
|
414
|
+
const transceiver = pc.addTransceiver(track, {
|
|
415
|
+
direction: 'sendrecv', // 'sendrecv' | 'sendonly' | 'recvonly' | 'inactive'
|
|
416
|
+
streams: [stream]
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Add transceiver with media kind
|
|
420
|
+
const videoTransceiver = pc.addTransceiver('video', {
|
|
421
|
+
direction: 'recvonly'
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Get all transceivers
|
|
425
|
+
const transceivers = pc.getTransceivers();
|
|
426
|
+
|
|
427
|
+
// Create offer
|
|
428
|
+
const offer = await pc.createOffer();
|
|
429
|
+
await pc.setLocalDescription(offer);
|
|
430
|
+
|
|
431
|
+
// Create answer
|
|
432
|
+
const answer = await pc.createAnswer();
|
|
433
|
+
await pc.setLocalDescription(answer);
|
|
434
|
+
|
|
435
|
+
// Set remote description
|
|
436
|
+
await pc.setRemoteDescription(answer);
|
|
437
|
+
|
|
438
|
+
// Add ICE candidate
|
|
439
|
+
await pc.addIceCandidate(candidate);
|
|
440
|
+
|
|
441
|
+
// Get connection state
|
|
442
|
+
const connectionState = pc.connectionState; // 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed'
|
|
443
|
+
const iceGatheringState = pc.iceGatheringState; // 'new' | 'gathering' | 'complete'
|
|
444
|
+
|
|
445
|
+
// Get local/remote description
|
|
446
|
+
const localDesc = pc.localDescription;
|
|
447
|
+
const remoteDesc = pc.remoteDescription;
|
|
448
|
+
|
|
449
|
+
// Event listeners
|
|
450
|
+
pc.onicecandidate = (event) => { };
|
|
451
|
+
pc.ontrack = (event) => { };
|
|
452
|
+
pc.onconnectionstatechange = (event) => { };
|
|
453
|
+
pc.onicegatheringstatechange = (event) => { };
|
|
454
|
+
|
|
455
|
+
// Close connection
|
|
456
|
+
pc.close();
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### MediaRecorder
|
|
460
|
+
|
|
461
|
+
Media recording
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import { MediaRecorder } from 'react-native-webrtc-nitro';
|
|
465
|
+
import RNFS from 'react-native-fs';
|
|
466
|
+
|
|
467
|
+
// Create recorder
|
|
468
|
+
const recorder = new MediaRecorder(stream);
|
|
469
|
+
|
|
470
|
+
// Take photo
|
|
471
|
+
const photoPath = `${RNFS.DocumentDirectoryPath}/photo.jpg`;
|
|
472
|
+
await recorder.takePhoto(photoPath);
|
|
473
|
+
|
|
474
|
+
// Start recording video
|
|
475
|
+
const videoPath = `${RNFS.DocumentDirectoryPath}/video.mp4`;
|
|
476
|
+
recorder.startRecording(videoPath);
|
|
477
|
+
|
|
478
|
+
// Stop recording
|
|
479
|
+
recorder.stopRecording();
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## 🏗️ Architecture
|
|
483
|
+
|
|
484
|
+
```
|
|
485
|
+
react-native-webrtc-nitro
|
|
486
|
+
├── src/ # TypeScript API
|
|
487
|
+
│ ├── specs/ # Nitro spec definitions
|
|
488
|
+
│ └── views/ # View components
|
|
489
|
+
├── cpp/ # C++ core implementation
|
|
490
|
+
│ ├── FFmpeg/ # FFmpeg wrapper
|
|
491
|
+
│ ├── Hybrid/ # Hybrid object implementations
|
|
492
|
+
│ └── FramePipe/ # Frame processing pipeline
|
|
493
|
+
├── ios/ # iOS platform code
|
|
494
|
+
├── android/ # Android platform code
|
|
495
|
+
└── 3rdparty/ # Third-party libraries
|
|
496
|
+
├── ffmpeg/
|
|
497
|
+
├── libdatachannel/
|
|
498
|
+
└── opus/
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## 🔧 Development
|
|
502
|
+
|
|
503
|
+
### Setup Development Environment
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
# Clone repository
|
|
507
|
+
git clone https://github.com/SingTown/react-native-webrtc-nitro.git
|
|
508
|
+
cd react-native-webrtc-nitro
|
|
509
|
+
|
|
510
|
+
# Build native libraries first
|
|
511
|
+
cd 3rdparty
|
|
512
|
+
|
|
513
|
+
# Download dependencies
|
|
514
|
+
./download.sh
|
|
515
|
+
|
|
516
|
+
# Build for iOS
|
|
517
|
+
./build_ios.sh
|
|
518
|
+
|
|
519
|
+
# Build for Android
|
|
520
|
+
./build_android.sh
|
|
521
|
+
|
|
522
|
+
cd ..
|
|
523
|
+
|
|
524
|
+
# Install dependencies
|
|
525
|
+
pnpm install
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Run Example
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
cd example
|
|
532
|
+
|
|
533
|
+
# iOS
|
|
534
|
+
pnpm ios
|
|
535
|
+
|
|
536
|
+
# Android
|
|
537
|
+
pnpm android
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## 📝 Example Project
|
|
541
|
+
|
|
542
|
+
Check the `example` directory for a complete sample application, including:
|
|
543
|
+
|
|
544
|
+
- Audio/video calls
|
|
545
|
+
- Media recording
|
|
546
|
+
- Multi-party conferencing
|
|
547
|
+
|
|
548
|
+
## 🤝 Contributing
|
|
549
|
+
|
|
550
|
+
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
|
|
551
|
+
|
|
552
|
+
1. Fork the repository
|
|
553
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
554
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
555
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
556
|
+
5. Open a Pull Request
|
|
557
|
+
|
|
558
|
+
## 📄 License
|
|
559
|
+
|
|
560
|
+
MIT License - see the [LICENSE](LICENSE) file for details
|
|
561
|
+
|
|
562
|
+
## 🙏 Acknowledgments
|
|
563
|
+
|
|
564
|
+
- [Nitro Modules](https://github.com/mrousavy/nitro) - Powerful React Native native module framework
|
|
565
|
+
- [libdatachannel](https://github.com/paullouisageneau/libdatachannel) - WebRTC data channel implementation
|
|
566
|
+
- [FFmpeg](https://ffmpeg.org/) - Audio/video processing library
|
|
567
|
+
|
|
568
|
+
## 📮 Contact
|
|
569
|
+
|
|
570
|
+
- GitHub: [@SingTown](https://github.com/SingTown)
|
|
571
|
+
- Issues: [Submit an issue](https://github.com/SingTown/react-native-webrtc-nitro/issues)
|
|
572
|
+
|
|
573
|
+
## 🌟 Star History
|
|
24
574
|
|
|
25
|
-
|
|
575
|
+
If this project helps you, please give us a ⭐️!
|
|
26
576
|
|
|
27
|
-
|
|
577
|
+
---
|
|
28
578
|
|
|
29
|
-
|
|
579
|
+
Built with ❤️ using [Nitro Modules](https://github.com/mrousavy/nitro)
|
|
@@ -35,7 +35,6 @@ class HybridMicrophone : HybridMicrophoneSpec() {
|
|
|
35
35
|
override fun open(pipeId: String): Promise<Unit> {
|
|
36
36
|
this.pipeId = pipeId
|
|
37
37
|
return Promise.async {
|
|
38
|
-
requestPermission(android.Manifest.permission.RECORD_AUDIO)
|
|
39
38
|
|
|
40
39
|
audioRecord = AudioRecord(
|
|
41
40
|
MediaRecorder.AudioSource.VOICE_COMMUNICATION,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package com.webrtc
|
|
2
|
+
|
|
3
|
+
import android.content.pm.PackageManager
|
|
4
|
+
import androidx.core.content.ContextCompat
|
|
5
|
+
import androidx.annotation.Keep
|
|
6
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
7
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
8
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
9
|
+
import kotlin.random.Random
|
|
10
|
+
import kotlinx.coroutines.CompletableDeferred
|
|
11
|
+
import kotlinx.coroutines.Dispatchers
|
|
12
|
+
import kotlinx.coroutines.withContext
|
|
13
|
+
import com.margelo.nitro.NitroModules
|
|
14
|
+
import com.margelo.nitro.core.Promise
|
|
15
|
+
import com.margelo.nitro.webrtc.HybridPermissionsSpec
|
|
16
|
+
import com.margelo.nitro.webrtc.PermissionDescriptor
|
|
17
|
+
import com.margelo.nitro.webrtc.PermissionState
|
|
18
|
+
import com.margelo.nitro.webrtc.PermissionName
|
|
19
|
+
|
|
20
|
+
@Keep
|
|
21
|
+
@DoNotStrip
|
|
22
|
+
class HybridPermissions : HybridPermissionsSpec() {
|
|
23
|
+
|
|
24
|
+
override fun query(permissionDesc: PermissionDescriptor): Promise<PermissionState> {
|
|
25
|
+
return Promise.async {
|
|
26
|
+
val permission = when (permissionDesc.name) {
|
|
27
|
+
PermissionName.MICROPHONE -> android.Manifest.permission.RECORD_AUDIO
|
|
28
|
+
PermissionName.CAMERA -> android.Manifest.permission.CAMERA
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
val context = NitroModules.applicationContext
|
|
32
|
+
?: throw RuntimeException("ReactApplicationContext is not available")
|
|
33
|
+
|
|
34
|
+
val currentActivity = context.currentActivity
|
|
35
|
+
?: throw RuntimeException("No current Activity")
|
|
36
|
+
val status = ContextCompat.checkSelfPermission(context, permission)
|
|
37
|
+
|
|
38
|
+
if (status == PackageManager.PERMISSION_GRANTED) {
|
|
39
|
+
PermissionState.GRANTED
|
|
40
|
+
} else if (status == PackageManager.PERMISSION_DENIED && !currentActivity.shouldShowRequestPermissionRationale(
|
|
41
|
+
permission
|
|
42
|
+
)
|
|
43
|
+
) {
|
|
44
|
+
PermissionState.PROMPT
|
|
45
|
+
} else {
|
|
46
|
+
PermissionState.DENIED
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun request(permissionDesc: PermissionDescriptor): Promise<PermissionState> {
|
|
52
|
+
return Promise.async {
|
|
53
|
+
val permission = when (permissionDesc.name) {
|
|
54
|
+
PermissionName.MICROPHONE -> android.Manifest.permission.RECORD_AUDIO
|
|
55
|
+
PermissionName.CAMERA -> android.Manifest.permission.CAMERA
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
val context = NitroModules.applicationContext
|
|
59
|
+
?: throw RuntimeException("ReactApplicationContext is not available")
|
|
60
|
+
|
|
61
|
+
val currentActivity = context.currentActivity
|
|
62
|
+
?: throw RuntimeException("No current Activity")
|
|
63
|
+
|
|
64
|
+
if (currentActivity !is PermissionAwareActivity) {
|
|
65
|
+
throw RuntimeException("Current activity doesn't support permissions")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
val code = Random.nextInt(0, 1000)
|
|
69
|
+
val deferred = CompletableDeferred<PermissionState>()
|
|
70
|
+
|
|
71
|
+
val listener = object : PermissionListener {
|
|
72
|
+
override fun onRequestPermissionsResult(
|
|
73
|
+
requestCode: Int,
|
|
74
|
+
permissions: Array<String>,
|
|
75
|
+
grantResults: IntArray
|
|
76
|
+
): Boolean {
|
|
77
|
+
if (requestCode != code) return false
|
|
78
|
+
|
|
79
|
+
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
80
|
+
deferred.complete(PermissionState.GRANTED)
|
|
81
|
+
} else {
|
|
82
|
+
deferred.complete(PermissionState.DENIED)
|
|
83
|
+
}
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
withContext(Dispatchers.Main) {
|
|
89
|
+
currentActivity.requestPermissions(arrayOf(permission), code, listener)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
deferred.await()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|