Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
98241e1
Ensure both video and audio tracks are added before starting the muxe…
jawad1257 Jul 10, 2025
ad4f320
Update to m137 with audio engine (#1875)
hiroshihorie Jul 18, 2025
6f5bbfb
Fix compile warnings (#1887)
hiroshihorie Jul 21, 2025
cf4b49e
fix: Removed outdated code to avoid UI not being displayed in Windows…
cloudwebrtc Jul 24, 2025
fc20336
Add audio recording for Android Platform (#1884)
maloleroy Jul 25, 2025
41dd6b3
Video recording crashing and freezing on Android 14 Devices (#1886)
daniel-g-favoreto-opl Jul 25, 2025
88694de
Fix: typo in package description (#1895)
ekasetiawans Jul 25, 2025
b85de10
feat: Upgrade libwebrtc to m137. (#1877)
cloudwebrtc Jul 25, 2025
d3ad54e
release: 1.0.0. (#1896)
cloudwebrtc Jul 25, 2025
8e3efdb
feat: Remove built-in binaries for win/linux.
cloudwebrtc Jul 25, 2025
882c01f
RECORDINGS - Add fallback resolutions for unsupported stream frame si…
daniel-g-favoreto-opl Jul 28, 2025
2eb041d
Update proguard-rules.pro (#1902)
gktirkha Jul 28, 2025
b68a73c
feat: Add texture-based video rendering for web (#1911)
theniceboy Aug 7, 2025
2cbc177
small setVolume addition (#1904)
mroccultus Aug 20, 2025
ae26d09
Reduce Recording Stop Delay and Prevent Encoder OOM Crashes (Android)…
daniel-g-favoreto-opl Aug 20, 2025
f276704
bump libwebrtc to 137.7151.03. (#1915)
cloudwebrtc Aug 20, 2025
dd62abe
write logs with Logger (logger package) (#1891)
Slash333 Aug 20, 2025
b7b02ce
Update build.yml
cloudwebrtc Aug 21, 2025
a955a42
fix android build.
cloudwebrtc Aug 21, 2025
46dcd9e
release: 1.1.0. (#1916)
cloudwebrtc Aug 21, 2025
9b6a89d
dart format.
cloudwebrtc Aug 21, 2025
4165493
upgrade compileSdk to 36 as standard for 16kb pages support (#1925)
shivanshtalwar0 Sep 8, 2025
a192ea9
Local recording API for Darwin and Android (#1880)
hiroshihorie Sep 9, 2025
362619a
feat: data packet cryptor. (#1927)
cloudwebrtc Sep 13, 2025
e254c41
Merge branch 'original_main' into chore/sync_1.2.0
Brazol Oct 2, 2025
f702ad1
webrtc m137
Brazol Oct 9, 2025
cde42cc
dart format fix
Brazol Oct 9, 2025
25b7a96
dart analyze fix
Brazol Oct 9, 2025
01b7f8f
restore missing PlaybackSampleAdapter
Brazol Oct 9, 2025
7b1dd5d
macos webrtc import fix
Brazol Oct 10, 2025
cfb7e55
update ios webrtc
Brazol Oct 29, 2025
1a595d2
Merge branch 'main' into chore/sync_1.2.0
Brazol Oct 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ jobs:
channel: 'stable'
- name: Install project dependencies
run: flutter pub get
- name: Install xcode platform support for iOS
run: xcodebuild -downloadPlatform iOS
- name: Build for iOS
working-directory: ./example
run: flutter build ios --release --no-codesign
Expand Down
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ example/ios/Flutter/flutter_export_environment.sh
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
Expand All @@ -56,4 +64,5 @@ android/.settings/org.eclipse.buildship.core.prefs
!webrtc.iml

# vs
*.pdb
*.pdb
ios/Frameworks
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@

# Changelog

[1.1.0]
* Synced flutter-webrtc v0.14.2
* [Doc] fix: typo in package description (#1895)
* [Android] fix: Video recording crashing and freezing on Android 14 Devices (#1886)
* [Android] fix: Add audio recording for Android Platform (#1884)
* [Dart] fix: Removed outdated code to avoid UI not being displayed in Windows release mode (#1890)
* [Apple] fix: Fix compile warnings (#1887)
* [Android] fix: Ensure both video and audio tracks are added before starting the muxer (#1879)
* [Apple/Android] feat: Add H265/HEVC support.
* [Mobile/Desktop] feat: Support write logs with Logger (logger package) (#1891)
* [Android] fix: Reduce Recording Stop Delay and Prevent Encoder OOM Crashes (Android) (#1912)
* [Native/Web] feat: small setVolume addition (#1904)
* [Web] feat: Add texture-based video rendering for web (#1911)
* [Android] fix: RECORDINGS - Add fallback resolutions for unsupported stream frame sizes on low-end Android devices (#1900)
* [Android] fix: Update proguard-rules.pro (#1902)
* [Android] upgrade compileSdk to 36 as standard for 16kb pages support (#1925)
* [Apple/Android] Local recording API for Darwin and Android (#1880)
* [Apple/Android] Data Packet Cryptor Support.

[1.0.13] - 2025-10-28
* Exposed `eventStream` in `MediaDevices`.
* [Android] Sends event when screen capture ends.
Expand Down
6 changes: 3 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ android {
if (project.android.hasProperty("namespace")) {
namespace 'io.getstream.webrtc.flutter'
}
compileSdkVersion 31
compileSdkVersion 36

defaultConfig {
minSdkVersion 21
Expand All @@ -47,12 +47,12 @@ android {
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = JavaVersion.VERSION_1_8
}
}

dependencies {
implementation 'io.getstream:stream-webrtc-android:1.3.8'
implementation 'io.getstream:stream-video-webrtc-android:137.0.1'
implementation 'com.github.davidliu:audioswitch:89582c47c9a04c62f90aa5e57251af4800a62c9a'
implementation 'androidx.annotation:annotation:1.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
Expand Down
3 changes: 1 addition & 2 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

1 change: 1 addition & 0 deletions android/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Flutter WebRTC
-keep class io.getstream.webrtc.flutter.** { *; }
-keep class org.webrtc.** { *; }
-keep class org.jni_zero.** { *; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package io.getstream.webrtc.flutter;

import androidx.annotation.NonNull;

import io.getstream.webrtc.flutter.utils.ConstraintsMap;

import org.webrtc.DataPacketCryptor;
import org.webrtc.DataPacketCryptorFactory;
import org.webrtc.FrameCryptor;
import org.webrtc.FrameCryptorAlgorithm;
import org.webrtc.FrameCryptorKeyProvider;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

class FlutterDataPacketCryptor {
private static final String TAG = "FlutterDataPacketCryptor";
private final Map<String, DataPacketCryptor> dataCryptos = new HashMap<>();

private final FlutterRTCFrameCryptor frameCryptor;

public FlutterDataPacketCryptor(FlutterRTCFrameCryptor frameCryptor) {
this.frameCryptor = frameCryptor;
}

public boolean handleMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
String method_name = call.method;
Map<String, Object> params = (Map<String, Object>) call.arguments;
if (method_name.equals("createDataPacketCryptor")) {
createDataPacketCryptor(params, result);
} else if (method_name.equals("dataPacketCryptorEncrypt")) {
dataPacketCryptorEncrypt(params, result);
} else if (method_name.equals("dataPacketCryptorDecrypt")) {
dataPacketCryptorDecrypt(params, result);
} else if (method_name.equals("dataPacketCryptorDispose")) {
dataPacketCryptorDispose(params, result);
} else {
return false;
}
return true;
}

private void createDataPacketCryptor(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
String keyProviderId = (String) params.get("keyProviderId");
FrameCryptorKeyProvider keyProvider = frameCryptor.getKeyProvider(keyProviderId);
if (keyProvider == null) {
result.error("createDataPacketCryptorFailed", "keyProvider not found", null);
return;
}

if (params.get("algorithm") == null) {
result.error("createDataPacketCryptorFailed", "algorithm is null", null);
return;
}

int algorithm = (int) params.get("algorithm");

DataPacketCryptor dataPacketCryptor = DataPacketCryptorFactory.createDataPacketCryptor(
frameCryptor.frameCryptorAlgorithmFromInt(algorithm),
keyProvider);
if (dataPacketCryptor == null) {
result.error("createDataPacketCryptorFailed", "createDataPacketCryptor failed", null);
return;
}

String dataCryptorId = UUID.randomUUID().toString();
dataCryptos.put(dataCryptorId, dataPacketCryptor);

ConstraintsMap paramsResult = new ConstraintsMap();
paramsResult.putString("dataCryptorId", dataCryptorId);
result.success(paramsResult.toMap());
}

private void dataPacketCryptorEncrypt(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
String dataCryptorId = (String) params.get("dataCryptorId");
if (dataCryptorId == null) {
result.error("dataPacketCryptorEncryptFailed", "dataCryptorId is null", null);
return;
}

DataPacketCryptor dataPacketCryptor = dataCryptos.get(dataCryptorId);
if (dataPacketCryptor == null) {
result.error("dataPacketCryptorEncryptFailed", "dataPacketCryptor not found", null);
return;
}

String participantId = (String) params.get("participantId");
if (participantId == null) {
result.error("dataPacketCryptorEncryptFailed", "participantId is null", null);
return;
}

byte[] data = (byte[]) params.get("data");
if (data == null) {
result.error("dataPacketCryptorEncryptFailed", "data is null", null);
return;
}

int keyIndex = (int) params.get("keyIndex");
if (keyIndex < 0) {
result.error("dataPacketCryptorEncryptFailed", "keyIndex is invalid", null);
return;
}

DataPacketCryptor.EncryptedPacket packet = dataPacketCryptor.encrypt(participantId, keyIndex, data);
ConstraintsMap paramsResult = new ConstraintsMap();
paramsResult.putInt("keyIndex", packet.keyIndex);
paramsResult.putByte("data", packet.payload);
paramsResult.putByte("iv", packet.iv);
result.success(paramsResult.toMap());
}

private void dataPacketCryptorDecrypt(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
String dataCryptorId = (String) params.get("dataCryptorId");
if (dataCryptorId == null) {
result.error("dataPacketCryptorEncryptFailed", "dataCryptorId is null", null);
return;
}

DataPacketCryptor dataPacketCryptor = dataCryptos.get(dataCryptorId);
if (dataPacketCryptor == null) {
result.error("dataPacketCryptorEncryptFailed", "dataPacketCryptor not found", null);
return;
}

String participantId = (String) params.get("participantId");
if (participantId == null) {
result.error("dataPacketCryptorEncryptFailed", "participantId is null", null);
return;
}

byte[] data = (byte[]) params.get("data");
if (data == null) {
result.error("dataPacketCryptorEncryptFailed", "data is null", null);
return;
}

byte[] iv = (byte[]) params.get("iv");
if (iv == null) {
result.error("dataPacketCryptorEncryptFailed", "iv is null", null);
return;
}

int keyIndex = (int) params.get("keyIndex");
if (keyIndex < 0) {
result.error("dataPacketCryptorEncryptFailed", "keyIndex is invalid", null);
return;
}
DataPacketCryptor.EncryptedPacket encryptedPacket = new DataPacketCryptor.EncryptedPacket(data, iv, keyIndex);
byte[] decrypted = dataPacketCryptor.decrypt(participantId, encryptedPacket);
if (decrypted == null) {
result.error("dataPacketCryptorDecryptFailed", "decrypt failed", null);
return;
}

ConstraintsMap paramsResult = new ConstraintsMap();
paramsResult.putByte("data", decrypted);
result.success(paramsResult.toMap());
}

private void dataPacketCryptorDispose(@NonNull Map<String, Object> params, @NonNull MethodChannel.Result result) {
String dataCryptorId = (String) params.get("dataCryptorId");
if (dataCryptorId == null) {
result.error("dataPacketCryptorDisposeFailed", "dataCryptorId is null", null);
return;
}

DataPacketCryptor dataPacketCryptor = dataCryptos.remove(dataCryptorId);
if (dataPacketCryptor == null) {
result.error("dataPacketCryptorDisposeFailed", "dataPacketCryptor not found", null);
return;
}

if (dataPacketCryptor != null) {
dataPacketCryptor.dispose();
}
result.success(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public boolean handleMethodCall(MethodCall call, @NonNull Result result) {
return true;
}

private FrameCryptorAlgorithm frameCryptorAlgorithmFromInt(int algorithm) {
public FrameCryptorAlgorithm frameCryptorAlgorithmFromInt(int algorithm) {
switch (algorithm) {
case 0:
return FrameCryptorAlgorithm.AES_GCM;
Expand Down Expand Up @@ -210,7 +210,6 @@ private void frameCryptorFactoryCreateFrameCryptor(Map<String, Object> params, @
result.success(paramsResult.toMap());
} else {
result.error("frameCryptorFactoryCreateFrameCryptorFailed", "type must be sender or receiver", null);
return;
}
}

Expand Down Expand Up @@ -431,4 +430,8 @@ private void keyProviderDispose(Map<String, Object> params, @NonNull Result resu
paramsResult.putString("result", "success");
result.success(paramsResult.toMap());
}

public FrameCryptorKeyProvider getKeyProvider(String id) {
return keyProviders.get(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ public class FlutterWebRTCPlugin implements FlutterPlugin, ActivityAware, EventC
private LifeCycleObserver observer;
private Lifecycle lifecycle;
private EventChannel eventChannel;
public EventChannel.EventSink eventSink;

// eventSink is static because FlutterWebRTCPlugin can be instantiated multiple times
// but the onListen(Object, EventChannel.EventSink) event only fires once for the first
// FlutterWebRTCPlugin instance, so for the next instances eventSink will be == null
public static EventChannel.EventSink eventSink;

public FlutterWebRTCPlugin() {
if (sharedSingleton == null) {
Expand Down
Loading