Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
a1bb18a
ios: add support for external cameras on iPad
mtdxc Jul 10, 2024
c0ddefd
ci: remove flipper from gumtestapp (#1608)
davidliu Aug 7, 2024
fb02a5b
pc: align createDataChannel with standard
saghul Aug 6, 2024
4c34ae1
android: report actual size in camera MediaStreamTrack settings (#1598)
davidliu Aug 7, 2024
f6667c8
ios: fix exception in iOS 17+ w/ Xcode 15.4
mtdxc Aug 7, 2024
ac7f578
android: remove no longer used replace rule from manifest (#1609)
saghul Aug 7, 2024
6cfedd7
sender: fix serializing RTCRtpSendParameters
saghul Aug 14, 2024
89557ca
misc: make serialization more resilient
saghul Aug 14, 2024
fb026de
release 124.0.4
saghul Aug 14, 2024
609c0b5
ios: Add RTCAudioSession helper methods needed for CallKit (#1614)
davidliu Aug 16, 2024
f3f3301
ios,android: add device/groupId to MediaStreamTrack.getSettings and i…
davidliu Aug 26, 2024
8bd5e0a
android: don't use minSdkVersion set by app (#1625)
davidliu Sep 17, 2024
4f337cd
doc: remove duplicate / confusing information about screen-sharing (#…
saghul Nov 4, 2024
b62727a
ios: add background camera access option
davidliu Dec 2, 2024
996c128
ios: picture in picture implementation
davidliu Dec 13, 2024
036e374
android: add libwebrtc consumer proguard rules
davidliu Dec 20, 2024
359caea
android: document how to set audio category to media
msabaeian Jan 4, 2025
803e720
release 124.0.5
saghul Jan 7, 2025
4c5d0f9
audit: update dependencies
saghul Jan 7, 2025
918866a
tools: tweak release message
saghul Jan 7, 2025
7a0b713
ci: update GHA versions
saghul Jan 7, 2025
19ca31d
ci: fix boost checksum error preventing builds (#1672)
davidliu Feb 3, 2025
76bc08a
tools: delete tools/build-webrtc.py
saghul Feb 20, 2025
8035eb5
Update build.gradle
Dimon70007 Apr 1, 2025
90c1815
refactor: improve type safety in RTCPeerConnection's getSenders and g…
yoohaso Apr 11, 2025
a388aba
fix: improve RTCPIPView component forwardRef type definition to remov…
yoohaso Apr 28, 2025
94175ed
Multiple video frame processors + implementation for ios (#1681)
8BallBomBom Jun 27, 2025
f515ee5
fix(android): Compatibility with RN 0.80+
oliverlaz Jul 23, 2025
c1ac950
ts: remove mandatory from sessionConstraints in docs, add createOffer…
mrakesh0608 Jul 25, 2025
cadefa5
Feature/video dimensions (#1724)
hmeerlo Jul 25, 2025
2f09f2d
misc: format
saghul Jul 28, 2025
0f1a265
release: 124.0.6
saghul Jul 28, 2025
de82ad3
android: fix NPE in gUM
saghul Aug 6, 2025
bf0c944
pc: handle null when normalizing options
saghul Aug 13, 2025
0ab26cb
fix: typo in _validatePermissionDescriptor method name
yoohaso Sep 5, 2025
ea08b32
Update building WebRTC docs
kota-yata Sep 20, 2025
503ec7f
ios: fix crash in iOS 26 simulator
saghul Oct 7, 2025
5ecc861
release: 124.0.7
saghul Oct 9, 2025
5ba65ce
android: fix ANR in getVideoTrackForStreamURL
saghul Oct 27, 2025
72f9dfd
pc: add minBitrate to encoding parameters
martinxsliu Feb 19, 2026
5771fbd
api: throw error in addIceCandidate when peer connection is closed
naveenkirugulige Mar 4, 2026
d2aa171
refactor: update event-target-shim imports to remove '/index'
Mar 1, 2026
c43189a
refactor: replace defineEventAttribute with getter/setter methods for…
Mar 2, 2026
495b416
refactor: update event-target-shim imports to use local vendor path
Mar 13, 2026
d6c7a40
chore: remove event-target-shim dependency from package-lock.json
Mar 13, 2026
66e61ba
chore: ignore src/vendor in eslint
Mar 13, 2026
43c665d
style: make eslint happy
Mar 13, 2026
e36ddec
build(deps-dev): bump flatted from 3.2.7 to 3.4.2
dependabot[bot] Mar 19, 2026
a243f5e
build(deps-dev): bump picomatch from 2.3.1 to 2.3.2
dependabot[bot] Mar 25, 2026
643067f
pc: add mediaConstraints on getDisplayMedia
FredLuart Apr 9, 2026
68ca776
pc: add RTCCertificate support
jiyeyuran Apr 13, 2026
f7b6382
ios: use runtime camera checks on simulator for camera availability
kmagiera Apr 14, 2026
bc486df
build(deps-dev): bump minimatch from 3.1.2 to 3.1.5 (#1791)
dependabot[bot] Apr 30, 2026
fdc4771
build(deps-dev): bump @babel/plugin-transform-modules-systemjs (#1809)
dependabot[bot] May 9, 2026
7f851d5
build(deps-dev): bump js-yaml from 4.1.0 to 4.1.1 (#1773)
dependabot[bot] May 9, 2026
3655418
fix: preserve facing mode in applyConstraints
naveenkirugulige May 11, 2026
e5d8781
ios: fix trigger broadcast picker on new arch
Calinteodor Jun 4, 2026
c4ea2d3
build(deps-dev): bump js-yaml from 4.1.1 to 4.2.0 (#1818)
dependabot[bot] Jun 23, 2026
0c63622
registerGlobals: don't set RTCRtpSender/RTCRtpReceiver twice
fippo Jul 4, 2026
e8face3
dispose the peer connection after signalingState changes to "closed" …
fippo Jul 5, 2026
3537d38
build(deps-dev): bump @babel/core in /examples/GumTestApp_macOS (#1817)
dependabot[bot] Jul 5, 2026
5892a0f
ios(RCTWebRTC): enable Center Stage for devices that support it
Calinteodor Jul 8, 2026
0a89cc0
ci(ios): pin runner to macos-15 (Xcode 16.4)
Calinteodor Jul 8, 2026
78904d2
android: fix race condition in getDisplayMedia
saghul Mar 20, 2026
b9b5ded
release: 124.0.8
saghul Jul 21, 2026
cf6101c
build(deps-dev): bump js-yaml from 4.2.0 to 4.3.0
dependabot[bot] Jul 21, 2026
0483cc8
android: release intermediate video effect frames
HanSeonWoo Jul 28, 2026
78d3094
fix(android): guard against duplicate onActivityResult in screen capture
Jul 29, 2026
014a8cf
build: ship the vendored declarations in the typescript build
saghul Jul 31, 2026
d062e9b
build(deps-dev): bump browserslist from 4.24.3 to 4.28.8 (#1836)
dependabot[bot] Sep 3, 2026
8f5428a
build(deps-dev): bump brace-expansion from 1.1.11 to 1.1.18 (#1835)
dependabot[bot] Sep 3, 2026
7266a9b
build(deps-dev): bump js-yaml from 4.3.0 to 4.3.2
dependabot[bot] Sep 9, 2026
70ab274
webrtc: Update WebRTC to M155
jallamsetty1 Oct 13, 2026
580735e
release: 155.0.0
jallamsetty1 Oct 13, 2026
d049d06
android(consumer-rules): cover SDK release builds who run R8
Calinteodor Oct 20, 2026
a5c66c7
Revert "ios(RCTWebRTC): enable Center Stage for devices that support it"
Calinteodor Oct 20, 2026
423292b
android: fix getDisplayMedia crash on API < 31
Calinteodor Oct 20, 2026
fb662c9
release: 155.0.1
saghul Oct 20, 2026
8f25154
Merge upstream react-native-webrtc 155.0.1
davidliu Nov 3, 2026
3d3a0c9
android: fix ConcurrentModificationException from off-executor create…
davidliu Nov 5, 2026
444d977
clang formatting
davidliu Nov 5, 2026
103af81
android: stop RTCView drawing the previous track after a stream sw…
shorifulmia85 Nov 11, 2026
ee302fb
Merge upstream: stop RTCView drawing the previous track after a strea…
davidliu Nov 11, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ examples/GumTestApp_macOS/package-lock.json
lib/
src/*.js
android/.gradle
tmp/
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ android/build/
*.tgz
*.zip
.github
tmp/
16 changes: 16 additions & 0 deletions Documentation/BasicUsage.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ try {
};
```

### Using Media Constraints on getDisplayMedia (Android Only)

It is possible to use mediaConstraints on getDisplayMedia to restricts the user to capturing the default display using the custom boolean parameter `createConfigForDefaultDisplay`.
A resolution scale can also be applied using `resolutionScale` parameter. Value is a number between 0 and 1.

This configuration in only available for android, so will you have to add the 'android' key in constraints.

```javascript
const displayMediaStreamConstraints = {
android: {
createConfigForDefaultDisplay: true,
resolutionScale: 0.5,
},
};
```

## Destroying the Media Stream

Cycling all of the tracks and stopping them is more than enough to clean up after a call has finished.
Expand Down
1 change: 1 addition & 0 deletions android/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# WebRTC
-keep class livekit.org.webrtc.** { *; }
-keep class livekit.org.jni_zero.** { *; }
-dontwarn livekit.org.jni_zero.**
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.oney.WebRTCModule;

import android.util.Log;

import androidx.annotation.Nullable;
import androidx.core.util.Consumer;

Expand All @@ -10,6 +12,8 @@
import livekit.org.webrtc.VideoCapturer;

public abstract class AbstractVideoCaptureController {
private static final String TAG = AbstractVideoCaptureController.class.getSimpleName();

protected int targetWidth;
protected int targetHeight;
protected int targetFps;
Expand Down Expand Up @@ -78,9 +82,7 @@ public void startCapture() {
try {
videoCapturer.startCapture(targetWidth, targetHeight, targetFps);
} catch (RuntimeException e) {
// XXX This can only fail if we initialize the capturer incorrectly,
// which we don't. Thus, ignore any failures here since we trust
// ourselves.
Log.e(TAG, "Failed to start capture", e);
}
}

Expand Down
102 changes: 95 additions & 7 deletions android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjectionConfig;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;

Expand All @@ -16,6 +20,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
Expand All @@ -29,6 +34,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import livekit.org.webrtc.*;
Expand Down Expand Up @@ -59,6 +67,8 @@ class GetUserMediaImpl {

private Promise displayMediaPromise;
private Intent mediaProjectionPermissionResultData;
private boolean createConfigForDefaultDisplay = false;
private float resolutionScale = 1.0f;

GetUserMediaImpl(WebRTCModule webRTCModule, ReactApplicationContext reactContext) {
this.webRTCModule = webRTCModule;
Expand All @@ -69,6 +79,15 @@ class GetUserMediaImpl {
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
super.onActivityResult(activity, requestCode, resultCode, data);
if (requestCode == PERMISSION_REQUEST_CODE) {
// Guard against a duplicate onActivityResult dispatch. Some hosts (e.g.
// react-native-navigation) forward the activity result to every registered
// ActivityEventListener more than once, so this callback can fire twice for a
// single getDisplayMedia() request. The first pass consumes displayMediaPromise;
// a second pass would call reject()/resolve() on a null promise and crash.
if (displayMediaPromise == null) {
return;
}

if (resultCode != Activity.RESULT_OK) {
displayMediaPromise.reject("DOMException", "NotAllowedError");
displayMediaPromise = null;
Expand All @@ -77,10 +96,25 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,

mediaProjectionPermissionResultData = data;

ThreadUtils.runOnExecutor(() -> {
MediaProjectionService.launch(activity);
CompletableFuture<Void> launchFuture = MediaProjectionService.launch(activity);

// CompletableFuture.orTimeout() requires API 31; this is a no-op if already completed.
new Handler(Looper.getMainLooper())
.postDelayed(()
-> launchFuture.completeExceptionally(new TimeoutException()),
TimeUnit.SECONDS.toMillis(10));

launchFuture.whenCompleteAsync((value, error) -> {
if (error != null) {
Log.e(TAG, "Failed to start MediaProjection service", error);
displayMediaPromise.reject("DOMException", "AbortError");
displayMediaPromise = null;
mediaProjectionPermissionResultData = null;
return;
}

createScreenStream();
});
}, ThreadUtils.getExecutor());
}
}
});
Expand Down Expand Up @@ -282,7 +316,41 @@ public void accept(Exception e) {
}
}

void getDisplayMedia(Promise promise) {
void initializeConstraints(ReadableMap constraints) {
// Handle the incoming params

ReadableMap androidConstraints = null;
if (constraints.hasKey("android") && constraints.getType("android") == ReadableType.Map) {
androidConstraints = constraints.getMap("android");
}

// Default values
boolean createConfigForDefaultDisplay = false;
float scale = 1.0f;

if (androidConstraints != null) {
// MediaProjectionConfig need API level 34
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
&& androidConstraints.hasKey("createConfigForDefaultDisplay")
&& androidConstraints.getType("createConfigForDefaultDisplay") == ReadableType.Boolean) {
createConfigForDefaultDisplay = androidConstraints.getBoolean("createConfigForDefaultDisplay");
}
if (androidConstraints.hasKey("resolutionScale")
&& androidConstraints.getType("resolutionScale") == ReadableType.Number) {
scale = (float) androidConstraints.getDouble("resolutionScale");
}
}

this.createConfigForDefaultDisplay = createConfigForDefaultDisplay;
// Force the value in [0, 1]
this.resolutionScale = Math.max(0.0f, Math.min(1.0f, scale));

Log.d(TAG,
"initializeConstraints: createConfigForDefaultDisplay=" + this.createConfigForDefaultDisplay
+ " resolutionScale=" + this.resolutionScale);
}

void getDisplayMedia(final ReadableMap constraints, Promise promise) {
if (this.displayMediaPromise != null) {
promise.reject(new RuntimeException("Another operation is pending."));
return;
Expand All @@ -294,6 +362,8 @@ void getDisplayMedia(Promise promise) {
return;
}

this.initializeConstraints(constraints);

this.displayMediaPromise = promise;

MediaProjectionManager mediaProjectionManager =
Expand All @@ -304,8 +374,18 @@ void getDisplayMedia(Promise promise) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
currentActivity.startActivityForResult(
mediaProjectionManager.createScreenCaptureIntent(), PERMISSION_REQUEST_CODE);
if (createConfigForDefaultDisplay == true) {
// MediaProjectionConfig need API level 34
// Return mediaProjection which restricts the user to capturing the default display
currentActivity.startActivityForResult(
mediaProjectionManager.createScreenCaptureIntent(
MediaProjectionConfig.createConfigForDefaultDisplay()),
PERMISSION_REQUEST_CODE);
} else {
// Return mediaProjection which allows the user to decide which region is captured
currentActivity.startActivityForResult(
mediaProjectionManager.createScreenCaptureIntent(), PERMISSION_REQUEST_CODE);
}
}
});

Expand All @@ -315,6 +395,14 @@ public void run() {
}

private void createScreenStream() {
// A duplicate onActivityResult dispatch (see onActivityResult above) can schedule this more
// than once. The single-threaded executor runs them in order, so by the time a duplicate
// runs the first has already consumed displayMediaPromise. Bail out instead of dereferencing
// a null promise or creating a second screen stream.
if (displayMediaPromise == null) {
return;
}

VideoTrack track = createScreenTrack();

if (track == null) {
Expand Down Expand Up @@ -392,7 +480,7 @@ private VideoTrack createScreenTrack() {
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
ScreenCaptureController screenCaptureController = new ScreenCaptureController(
reactContext.getCurrentActivity(), width, height, mediaProjectionPermissionResultData);
reactContext.getCurrentActivity(), width, height, mediaProjectionPermissionResultData, resolutionScale);
return createVideoTrack(screenCaptureController);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.util.Log;

import java.util.Random;
import java.util.concurrent.CompletableFuture;

/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
Expand All @@ -24,11 +25,17 @@ public class MediaProjectionService extends Service {

static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;

public static void launch(Context context) {
private static volatile CompletableFuture<Void> startFuture;

public static CompletableFuture<Void> launch(Context context) {
if (!WebRTCModuleOptions.getInstance().enableMediaProjectionService) {
return;
return CompletableFuture.completedFuture(null);
}

CompletableFuture<Void> future = new CompletableFuture<>();

startFuture = future;

MediaProjectionNotification.createNotificationChannel(context);
Intent intent = new Intent(context, MediaProjectionService.class);
ComponentName componentName;
Expand All @@ -43,14 +50,21 @@ public static void launch(Context context) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
Log.w(TAG, "Media projection service not started", e);
return;
startFuture = null;
future.completeExceptionally(e);

return future;
}

if (componentName == null) {
Log.w(TAG, "Media projection service not started");
startFuture = null;
future.completeExceptionally(new RuntimeException("Media projection service not started"));
} else {
Log.i(TAG, "Media projection service started");
}

return future;
}

public static void abort(Context context) {
Expand All @@ -77,6 +91,13 @@ public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(NOTIFICATION_ID, notification);
}

CompletableFuture<Void> fut = startFuture;

if (fut != null) {
startFuture = null;
fut.complete(null);
}

return START_NOT_STICKY;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ public class ScreenCaptureController extends AbstractVideoCaptureController {

private final Context context;

public ScreenCaptureController(Context context, int width, int height, Intent mediaProjectionPermissionResultData) {
super(width, height, DEFAULT_FPS);
public ScreenCaptureController(
Context context, int width, int height, Intent mediaProjectionPermissionResultData, float resolutionScale) {
super((int) (width * resolutionScale), (int) (height * resolutionScale), DEFAULT_FPS);

this.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData;

Expand All @@ -36,8 +37,8 @@ public ScreenCaptureController(Context context, int width, int height, Intent me
@Override
public void onOrientationChanged(int orientation) {
DisplayMetrics displayMetrics = DisplayUtils.getDisplayMetrics((Activity) context);
final int width = displayMetrics.widthPixels;
final int height = displayMetrics.heightPixels;
final int width = (int) (displayMetrics.widthPixels * resolutionScale);
final int height = (int) (displayMetrics.heightPixels * resolutionScale);
if (width != ScreenCaptureController.this.actualWidth
|| height != ScreenCaptureController.this.actualHeight) {
ScreenCaptureController.this.actualWidth = width;
Expand Down
8 changes: 8 additions & 0 deletions android/src/main/java/com/oney/WebRTCModule/ThreadUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ final class ThreadUtils {
*/
private static final ExecutorService executor = Executors.newSingleThreadExecutor();

/**
* Gets the executor used to run all WebRTC PeerConnection APIs.
* @return ExecutorService.
*/
public static ExecutorService getExecutor() {
return executor;
}

/**
* Runs the given {@link Runnable} on the executor.
* @param runnable
Expand Down
Loading
Loading