Skip to content

Commit 362619a

Browse files
authored
feat: data packet cryptor. (flutter-webrtc#1927)
1 parent a192ea9 commit 362619a

26 files changed

+2176
-1094
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11

22
# Changelog
33

4+
[1.2.0] - 2025-09-13
5+
6+
* [Android] upgrade compileSdk to 36 as standard for 16kb pages support (#1925)
7+
* [Apple/Android] Local recording API for Darwin and Android (#1880)
8+
* [Apple/Android] Data Packet Cryptor Support.
9+
410
[1.1.0] - 2025-08-20
511

612
* [Apple/Android] feat: Add H265/HEVC support.

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ android {
5252
}
5353

5454
dependencies {
55-
implementation 'io.github.webrtc-sdk:android:137.7151.03'
55+
implementation 'io.github.webrtc-sdk:android:137.7151.04'
5656
implementation 'com.github.davidliu:audioswitch:89582c47c9a04c62f90aa5e57251af4800a62c9a'
5757
implementation 'androidx.annotation:annotation:1.1.0'
5858
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package com.cloudwebrtc.webrtc;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import com.cloudwebrtc.webrtc.utils.ConstraintsMap;
6+
7+
import org.webrtc.DataPacketCryptor;
8+
import org.webrtc.DataPacketCryptorFactory;
9+
import org.webrtc.FrameCryptor;
10+
import org.webrtc.FrameCryptorAlgorithm;
11+
import org.webrtc.FrameCryptorKeyProvider;
12+
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.UUID;
16+
17+
import io.flutter.plugin.common.MethodCall;
18+
import io.flutter.plugin.common.MethodChannel;
19+
20+
class FlutterDataPacketCryptor {
21+
private static final String TAG = "FlutterDataPacketCryptor";
22+
private final Map<String, DataPacketCryptor> dataCryptos = new HashMap<>();
23+
24+
private final FlutterRTCFrameCryptor frameCryptor;
25+
26+
public FlutterDataPacketCryptor(FlutterRTCFrameCryptor frameCryptor) {
27+
this.frameCryptor = frameCryptor;
28+
}
29+
30+
public boolean handleMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
31+
String method_name = call.method;
32+
Map<String, Object> params = (Map<String, Object>) call.arguments;
33+
if (method_name.equals("createDataPacketCryptor")) {
34+
createDataPacketCryptor(params, result);
35+
} else if(method_name.equals("dataPacketCryptorEncrypt")) {
36+
dataPacketCryptorEncrypt(params, result);
37+
} else if(method_name.equals("dataPacketCryptorDecrypt")) {
38+
dataPacketCryptorDecrypt(params, result);
39+
} else if(method_name.equals("dataPacketCryptorDispose")) {
40+
dataPacketCryptorDispose(params, result);
41+
} else {
42+
return false;
43+
}
44+
return true;
45+
}
46+
47+
private void createDataPacketCryptor(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
48+
String keyProviderId = (String) params.get("keyProviderId");
49+
FrameCryptorKeyProvider keyProvider = frameCryptor.getKeyProvider(keyProviderId);
50+
if (keyProvider == null) {
51+
result.error("createDataPacketCryptorFailed", "keyProvider not found", null);
52+
return;
53+
}
54+
55+
if(params.get("algorithm") == null) {
56+
result.error("createDataPacketCryptorFailed", "algorithm is null", null);
57+
return;
58+
}
59+
60+
int algorithm = (int) params.get("algorithm");
61+
62+
DataPacketCryptor dataPacketCryptor = DataPacketCryptorFactory.createDataPacketCryptor(
63+
frameCryptor.frameCryptorAlgorithmFromInt(algorithm),
64+
keyProvider);
65+
if(dataPacketCryptor == null) {
66+
result.error("createDataPacketCryptorFailed", "createDataPacketCryptor failed", null);
67+
return;
68+
}
69+
70+
String dataCryptorId = UUID.randomUUID().toString();
71+
dataCryptos.put(dataCryptorId, dataPacketCryptor);
72+
73+
ConstraintsMap paramsResult = new ConstraintsMap();
74+
paramsResult.putString("dataCryptorId", dataCryptorId);
75+
result.success(paramsResult.toMap());
76+
}
77+
78+
private void dataPacketCryptorEncrypt(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
79+
String dataCryptorId = (String) params.get("dataCryptorId");
80+
if (dataCryptorId == null) {
81+
result.error("dataPacketCryptorEncryptFailed", "dataCryptorId is null", null);
82+
return;
83+
}
84+
85+
DataPacketCryptor dataPacketCryptor = dataCryptos.get(dataCryptorId);
86+
if(dataPacketCryptor == null) {
87+
result.error("dataPacketCryptorEncryptFailed", "dataPacketCryptor not found", null);
88+
return;
89+
}
90+
91+
String participantId = (String) params.get("participantId");
92+
if (participantId == null) {
93+
result.error("dataPacketCryptorEncryptFailed", "participantId is null", null);
94+
return;
95+
}
96+
97+
byte[] data = (byte[]) params.get("data");
98+
if (data == null) {
99+
result.error("dataPacketCryptorEncryptFailed", "data is null", null);
100+
return;
101+
}
102+
103+
int keyIndex = (int) params.get("keyIndex");
104+
if( keyIndex < 0 ) {
105+
result.error("dataPacketCryptorEncryptFailed", "keyIndex is invalid", null);
106+
return;
107+
}
108+
109+
DataPacketCryptor.EncryptedPacket packet = dataPacketCryptor.encrypt(participantId, keyIndex, data);
110+
ConstraintsMap paramsResult = new ConstraintsMap();
111+
paramsResult.putInt("keyIndex", packet.keyIndex);
112+
paramsResult.putByte("data", packet.payload);
113+
paramsResult.putByte("iv", packet.iv);
114+
result.success(paramsResult.toMap());
115+
}
116+
117+
private void dataPacketCryptorDecrypt(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
118+
String dataCryptorId = (String) params.get("dataCryptorId");
119+
if (dataCryptorId == null) {
120+
result.error("dataPacketCryptorEncryptFailed", "dataCryptorId is null", null);
121+
return;
122+
}
123+
124+
DataPacketCryptor dataPacketCryptor = dataCryptos.get(dataCryptorId);
125+
if(dataPacketCryptor == null) {
126+
result.error("dataPacketCryptorEncryptFailed", "dataPacketCryptor not found", null);
127+
return;
128+
}
129+
130+
String participantId = (String) params.get("participantId");
131+
if (participantId == null) {
132+
result.error("dataPacketCryptorEncryptFailed", "participantId is null", null);
133+
return;
134+
}
135+
136+
byte[] data = (byte[]) params.get("data");
137+
if (data == null) {
138+
result.error("dataPacketCryptorEncryptFailed", "data is null", null);
139+
return;
140+
}
141+
142+
byte[] iv = (byte[]) params.get("iv");
143+
if (iv == null) {
144+
result.error("dataPacketCryptorEncryptFailed", "iv is null", null);
145+
return;
146+
}
147+
148+
int keyIndex = (int) params.get("keyIndex");
149+
if( keyIndex < 0 ) {
150+
result.error("dataPacketCryptorEncryptFailed", "keyIndex is invalid", null);
151+
return;
152+
}
153+
DataPacketCryptor.EncryptedPacket encryptedPacket = new DataPacketCryptor.EncryptedPacket(data, iv, keyIndex);
154+
byte[] decrypted = dataPacketCryptor.decrypt(participantId, encryptedPacket);
155+
if(decrypted == null) {
156+
result.error("dataPacketCryptorDecryptFailed", "decrypt failed", null);
157+
return;
158+
}
159+
160+
ConstraintsMap paramsResult = new ConstraintsMap();
161+
paramsResult.putByte("data", decrypted);
162+
result.success(paramsResult.toMap());
163+
}
164+
165+
private void dataPacketCryptorDispose(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
166+
String dataCryptorId = (String) params.get("dataCryptorId");
167+
if (dataCryptorId == null) {
168+
result.error("dataPacketCryptorDisposeFailed", "dataCryptorId is null", null);
169+
return;
170+
}
171+
172+
DataPacketCryptor dataPacketCryptor = dataCryptos.remove(dataCryptorId);
173+
if(dataPacketCryptor == null) {
174+
result.error("dataPacketCryptorDisposeFailed", "dataPacketCryptor not found", null);
175+
return;
176+
}
177+
178+
if (dataPacketCryptor != null) {
179+
dataPacketCryptor.dispose();
180+
}
181+
result.success(null);
182+
}
183+
}

android/src/main/java/com/cloudwebrtc/webrtc/FlutterRTCFrameCryptor.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public boolean handleMethodCall(MethodCall call, @NonNull Result result) {
148148
return true;
149149
}
150150

151-
private FrameCryptorAlgorithm frameCryptorAlgorithmFromInt(int algorithm) {
151+
public FrameCryptorAlgorithm frameCryptorAlgorithmFromInt(int algorithm) {
152152
switch (algorithm) {
153153
case 0:
154154
return FrameCryptorAlgorithm.AES_GCM;
@@ -210,7 +210,6 @@ private void frameCryptorFactoryCreateFrameCryptor(Map<String, Object> params, @
210210
result.success(paramsResult.toMap());
211211
} else {
212212
result.error("frameCryptorFactoryCreateFrameCryptorFailed", "type must be sender or receiver", null);
213-
return;
214213
}
215214
}
216215

@@ -431,4 +430,8 @@ private void keyProviderDispose(Map<String, Object> params, @NonNull Result resu
431430
paramsResult.putString("result", "success");
432431
result.success(paramsResult.toMap());
433432
}
433+
434+
public FrameCryptorKeyProvider getKeyProvider(String id) {
435+
return keyProviders.get(id);
436+
}
434437
}

android/src/main/java/com/cloudwebrtc/webrtc/GetUserMediaImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ public void requestStart(Activity activity, int requestCode) {
184184
Log.w(
185185
TAG,
186186
"Can't run requestStart() due to a low API level. API level 21 or higher is required.");
187-
return;
188187
} else {
189188
MediaProjectionManager mediaProjectionManager =
190189
(MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);

android/src/main/java/com/cloudwebrtc/webrtc/MethodCallHandlerImpl.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ public class MethodCallHandlerImpl implements MethodCallHandler, StateProvider {
131131

132132
private FlutterRTCFrameCryptor frameCryptor;
133133

134+
private FlutterDataPacketCryptor dataPacketCryptor;
135+
134136
private Activity activity;
135137

136138
private CustomVideoEncoderFactory videoEncoderFactory;
@@ -199,6 +201,8 @@ private void initialize(boolean bypassVoiceProcessing, int networkIgnoreMask, bo
199201

200202
frameCryptor = new FlutterRTCFrameCryptor(this);
201203

204+
dataPacketCryptor = new FlutterDataPacketCryptor(frameCryptor);
205+
202206
AudioAttributes audioAttributes = null;
203207
if (androidAudioConfiguration != null) {
204208
Integer usageType = AudioUtils.getAudioAttributesUsageTypeForString(
@@ -1070,6 +1074,8 @@ public void onMethodCall(MethodCall call, @NonNull Result notSafeResult) {
10701074
default:
10711075
if(frameCryptor.handleMethodCall(call, result)) {
10721076
break;
1077+
} else if(dataPacketCryptor.handleMethodCall(call, result)) {
1078+
break;
10731079
}
10741080
result.notImplemented();
10751081
break;
@@ -1619,7 +1625,7 @@ public void getSources(Result result) {
16191625
ConstraintsMap audioOutputMap = new ConstraintsMap();
16201626
audioOutputMap.putString("label", audioOutput.getName());
16211627
audioOutputMap.putString("deviceId", AudioDeviceKind.fromAudioDevice(audioOutput).typeName);
1622-
audioOutputMap.putString("groupId", "" + AudioDeviceKind.fromAudioDevice(audioOutput).typeName);
1628+
audioOutputMap.putString("groupId", AudioDeviceKind.fromAudioDevice(audioOutput).typeName);
16231629
audioOutputMap.putString("kind", "audiooutput");
16241630
array.pushMap(audioOutputMap);
16251631
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#if TARGET_OS_IPHONE
2+
#import <Flutter/Flutter.h>
3+
#elif TARGET_OS_OSX
4+
#import <FlutterMacOS/FlutterMacOS.h>
5+
#endif
6+
7+
#import <WebRTC/WebRTC.h>
8+
9+
#import "FlutterWebRTCPlugin.h"
10+
11+
@interface FlutterWebRTCPlugin (DataPacketCryptor)
12+
13+
- (void)handleDataPacketCryptorMethodCall:(nonnull FlutterMethodCall*)call
14+
result:(nonnull FlutterResult)result;
15+
16+
@end

0 commit comments

Comments
 (0)