From e84ca84920e65e7cef0a6bc43fbc9066b64d3c98 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Fri, 24 Apr 2026 13:48:47 +0200 Subject: [PATCH 1/5] feat: handle lifecycle on android video effects --- .../oney/WebRTCModule/GetUserMediaImpl.java | 20 +++++++++++++++++++ .../videoEffects/VideoEffectProcessor.java | 17 ++++++++++++++++ .../videoEffects/VideoFrameProcessor.java | 6 ++++++ 3 files changed, 43 insertions(+) diff --git a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java index 60856c7c6..b9c3e5fe3 100644 --- a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java +++ b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java @@ -584,6 +584,11 @@ void setVideoEffects(String trackId, ReadableArray names) { VideoSource videoSource = (VideoSource) track.mediaSource; SurfaceTextureHelper surfaceTextureHelper = track.surfaceTextureHelper; + // Swap first, dispose last — otherwise a frame in flight could hit a freed + // processor. onCapturerStopped can't replace this; it also fires on pauses. + VideoEffectProcessor previousProcessor = track.videoEffectProcessor; + track.videoEffectProcessor = null; + if (names != null) { List processors = names.toArrayList() @@ -602,10 +607,15 @@ void setVideoEffects(String trackId, ReadableArray names) { VideoEffectProcessor videoEffectProcessor = new VideoEffectProcessor(processors, surfaceTextureHelper); videoSource.setVideoProcessor(videoEffectProcessor); + track.videoEffectProcessor = videoEffectProcessor; } else { videoSource.setVideoProcessor(null); } + + if (previousProcessor != null) { + previousProcessor.dispose(); + } } } @@ -643,6 +653,9 @@ private static class TrackPrivate { */ public final VideoTrackAdapter videoTrackAdapter; + /** Current effect processor, disposed on filter switch and on track teardown. */ + public VideoEffectProcessor videoEffectProcessor; + /** * Whether this object has been disposed or not. */ @@ -693,6 +706,13 @@ public void dispose() { } } + // After stopCapture so no frame can still reach it; before + // surfaceTextureHelper dispose so GL is still alive for cleanup. + if (!isClone && videoEffectProcessor != null) { + videoEffectProcessor.dispose(); + videoEffectProcessor = null; + } + // Clean up VideoTrackAdapter for video tracks (each TrackPrivate, incl. clones, has its own) if (videoTrackAdapter != null && track instanceof VideoTrack) { videoTrackAdapter.removeDimensionDetector((VideoTrack) track); diff --git a/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java b/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java index 4edd62680..37dc43af4 100644 --- a/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java +++ b/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java @@ -15,6 +15,7 @@ public class VideoEffectProcessor implements VideoProcessor { private VideoSink mSink; final private SurfaceTextureHelper textureHelper; final private List videoFrameProcessors; + private boolean disposed = false; public VideoEffectProcessor(List processors, SurfaceTextureHelper textureHelper) { this.textureHelper = textureHelper; @@ -27,6 +28,22 @@ public void onCapturerStarted(boolean success) {} @Override public void onCapturerStopped() {} + /** + * Disposes each wrapped processor. Posted to the capturer handler so it runs + * after any in-flight {@link #onFrameCaptured}, letting implementations release + * GL resources inline. Idempotent. Called by the owner — not wired to + * {@link #onCapturerStopped}, which also fires on pauses. + */ + public void dispose() { + textureHelper.getHandler().post(() -> { + if (disposed) return; + disposed = true; + for (VideoFrameProcessor processor : this.videoFrameProcessors) { + processor.dispose(); + } + }); + } + @Override public void setSink(VideoSink sink) { mSink = sink; diff --git a/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoFrameProcessor.java b/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoFrameProcessor.java index b0a4c0cec..2a5c40706 100644 --- a/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoFrameProcessor.java +++ b/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoFrameProcessor.java @@ -16,4 +16,10 @@ public interface VideoFrameProcessor { * @return processed videoframe which will rendered */ public VideoFrame process(VideoFrame frame, SurfaceTextureHelper textureHelper); + + /** + * Called once when this processor leaves the pipeline. Release any native or + * GL resources here. Runs on the capturer handler. Default no-op. + */ + default void dispose() {} } From c79d5b29611d8bf50e92027481c0841381b99804 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Fri, 24 Apr 2026 13:55:54 +0200 Subject: [PATCH 2/5] 137.2.0-alpha.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ea350e71..1d7b64b87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@stream-io/react-native-webrtc", - "version": "137.2.0-alpha.3", + "version": "137.2.0-alpha.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@stream-io/react-native-webrtc", - "version": "137.2.0-alpha.3", + "version": "137.2.0-alpha.4", "license": "MIT", "dependencies": { "base64-js": "1.5.1", diff --git a/package.json b/package.json index 36a9e1f6c..91bb7c5c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/react-native-webrtc", - "version": "137.2.0-alpha.3", + "version": "137.2.0-alpha.4", "repository": { "type": "git", "url": "git+https://github.com/GetStream/react-native-webrtc.git" From 2bef066e5f707714959a81e78a846e165b930482 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Fri, 24 Apr 2026 14:53:16 +0200 Subject: [PATCH 3/5] doc minimise --- .../oney/WebRTCModule/videoEffects/VideoEffectProcessor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java b/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java index 37dc43af4..bb1269fac 100644 --- a/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java +++ b/android/src/main/java/com/oney/WebRTCModule/videoEffects/VideoEffectProcessor.java @@ -30,9 +30,8 @@ public void onCapturerStopped() {} /** * Disposes each wrapped processor. Posted to the capturer handler so it runs - * after any in-flight {@link #onFrameCaptured}, letting implementations release - * GL resources inline. Idempotent. Called by the owner — not wired to - * {@link #onCapturerStopped}, which also fires on pauses. + * after any in-flight frame, so implementations can clean up GL state inline. + * Idempotent. Not wired to {@link #onCapturerStopped} because that also fires on pause. */ public void dispose() { textureHelper.getHandler().post(() -> { From d250856fbdc797f5038a701d3a92ecae8506438e Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Fri, 24 Apr 2026 14:53:45 +0200 Subject: [PATCH 4/5] reenable camera flip through constraints --- .../WebRTCModule/CameraCaptureController.java | 8 +++--- ios/RCTWebRTC/VideoCaptureController.m | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/oney/WebRTCModule/CameraCaptureController.java b/android/src/main/java/com/oney/WebRTCModule/CameraCaptureController.java index 939d79350..020f0972c 100644 --- a/android/src/main/java/com/oney/WebRTCModule/CameraCaptureController.java +++ b/android/src/main/java/com/oney/WebRTCModule/CameraCaptureController.java @@ -119,9 +119,11 @@ public void applyConstraints(ReadableMap constraints, @Nullable Consumer Date: Fri, 24 Apr 2026 14:54:04 +0200 Subject: [PATCH 5/5] 137.2.0-alpha.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d7b64b87..e6b4aed70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@stream-io/react-native-webrtc", - "version": "137.2.0-alpha.4", + "version": "137.2.0-alpha.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@stream-io/react-native-webrtc", - "version": "137.2.0-alpha.4", + "version": "137.2.0-alpha.5", "license": "MIT", "dependencies": { "base64-js": "1.5.1", diff --git a/package.json b/package.json index 91bb7c5c3..8dfd94b46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/react-native-webrtc", - "version": "137.2.0-alpha.4", + "version": "137.2.0-alpha.5", "repository": { "type": "git", "url": "git+https://github.com/GetStream/react-native-webrtc.git"