From aa3cce2b987f126afdc19a9198366bea3ed5d781 Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Wed, 11 Feb 2026 11:41:29 +0100 Subject: [PATCH 1/9] refactor: simplify managing and creating tracks logic --- .../modules/stream/StreamApi.java | 112 ++---------------- 1 file changed, 8 insertions(+), 104 deletions(-) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java index c859e703..1bb11343 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java @@ -203,124 +203,28 @@ public StreamHandle createStream(String streamRoomId) { return handle; } - // - public List getMediaDevices() { - List result = new ArrayList<>(); - - AudioManager audioManager = (AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE); - CameraEnumerator videoManager; - - android.media.AudioDeviceInfo[] audioDevices = audioManager.getDevices(GET_DEVICES_OUTPUTS); - if (Camera2Enumerator.isSupported(appContext)) { - videoManager = new Camera2Enumerator(appContext); - } else { - videoManager = new Camera1Enumerator(true); - } - String[] videoDevices = videoManager.getDeviceNames(); - - audioManager.getActiveRecordingConfigurations().forEach(AudioRecordingConfiguration::getAudioDevice); - - List audio = Arrays.stream(audioDevices).map(it -> - new MediaDevice( - it.getProductName().toString(), - String.valueOf(it.getId()), - DeviceType.Audio - ) - ).collect(Collectors.toList()); - result.addAll(audio); - - List video = Arrays.stream(videoDevices).map(it -> - new MediaDevice( - it, - it, // todo: what else can it be? - DeviceType.Audio - ) - ).collect(Collectors.toList()); - result.addAll(video); - - return result; - } - - private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) { - final String[] deviceNames = enumerator.getDeviceNames(); - - // First, try to find front facing camera - Logging.d(TAG, "Looking for front facing cameras."); - for (String deviceName : deviceNames) { - if (enumerator.isFrontFacing(deviceName)) { - Logging.d(TAG, "Creating front facing camera capturer."); - VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); - - if (videoCapturer != null) { - return videoCapturer; - } - } - } - - // Front facing camera not found, try something else - Logging.d(TAG, "Looking for other cameras."); - for (String deviceName : deviceNames) { - if (!enumerator.isFrontFacing(deviceName)) { - Logging.d(TAG, "Creating other camera capturer."); - VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); - - if (videoCapturer != null) { - return videoCapturer; - } - } - } - - return null; - } - /** - * @param context - * @param localSink * @param streamHandle * @param track * @throws IllegalStateException if call addTrack before call createStream */ public void addTrack( - Context context, - VideoSink localSink, StreamHandle streamHandle, - MediaDevice track + MediaStreamTrack track ) throws IllegalStateException { RoomJanusSession session = pcManager.getSession(streamHandle); if (session == null) throw new IllegalStateException("Stream not exists. Create stream first."); - JanusPublisher connection = session.getPublisher(); - if (connection == null) + JanusPublisher publisher = session.getPublisher(); + if (publisher == null) throw new IllegalStateException("This StreamHandle has not created companion publisher."); - switch (track.type) { - case Audio: { - AudioSource audioSource = connection.peerConnectionFactory.createAudioSource(new MediaConstraints()); - AudioTrack audioTrack = connection.peerConnectionFactory.createAudioTrack(track.name, audioSource); - audioTrack.setVolume(10.0); - connection.addAudioTrack(audioTrack); + switch (track.kind()){ + case MediaStreamTrack.VIDEO_TRACK_KIND: { + publisher.addAudioTrack((AudioTrack) track); break; } - - case Video: { - SurfaceTextureHelper surfaceTextureHelper = - SurfaceTextureHelper.create("CaptureThread", rootEglBase.getEglBaseContext()); - VideoSource videoSource = connection.peerConnectionFactory.createVideoSource(false, false); // todo - zaimplementowac caly capturer? - VideoCapturer capturer = createCameraCapturer(new Camera2Enumerator(context)); - capturer.initialize(surfaceTextureHelper, appContext, videoSource.getCapturerObserver()); -// capturer.startCapture(1920, 1080, 30); - VideoTrack videoTrack = connection.peerConnectionFactory.createVideoTrack(track.name, videoSource); - videoTrack.setEnabled(true); - videoTrack.addSink(localSink); - connection.addVideoTrack(videoTrack,capturer); -// if (Camera2Enumerator.isSupported(appContext)) { -// enumerator = new Camera2Enumerator(appContext); -// } else { -// enumerator = new Camera1Enumerator(true); -// } -// capturer = enumerator.createCapturer(track.name, null); - - capturer.startCapture(1280, 720, 30); // ??? - System.out.println("after start capturer"); + case MediaStreamTrack.AUDIO_TRACK_KIND:{ + publisher.addVideoTrack((VideoTrack) track); break; } } From 6f74b2e60207259f2ad10ab4de545c2f6de6b49e Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Thu, 12 Feb 2026 12:59:42 +0100 Subject: [PATCH 2/9] feat: Implement TrackFactory class for creating sources and tracks on StreamHandle --- .../modules/stream/StreamApi.java | 36 ++++++--------- .../modules/stream/TrackFactory.java | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java index 1bb11343..4caa3863 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java @@ -1,57 +1,37 @@ package com.simplito.java.privmx_endpoint.modules.stream; -import static android.media.AudioManager.GET_DEVICES_OUTPUTS; - import android.content.Context; -import android.media.AudioManager; -import android.media.AudioRecordingConfiguration; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.simplito.java.privmx_endpoint.model.ContainerPolicy; -import com.simplito.java.privmx_endpoint.model.DeviceType; -import com.simplito.java.privmx_endpoint.model.MediaDevice; import com.simplito.java.privmx_endpoint.model.PagingList; import com.simplito.java.privmx_endpoint.model.Settings; import com.simplito.java.privmx_endpoint.model.StreamHandle; import com.simplito.java.privmx_endpoint.model.StreamInfo; import com.simplito.java.privmx_endpoint.model.StreamPublishResult; import com.simplito.java.privmx_endpoint.model.StreamRoom; -import com.simplito.java.privmx_endpoint.model.StreamSettings; import com.simplito.java.privmx_endpoint.model.StreamSubscription; import com.simplito.java.privmx_endpoint.model.UserWithPubKey; import com.simplito.java.privmx_endpoint.model.events.eventSelectorTypes.StreamEventSelectorType; import com.simplito.java.privmx_endpoint.model.events.eventTypes.StreamEventType; -import org.webrtc.AudioSource; import org.webrtc.AudioTrack; -import org.webrtc.Camera1Enumerator; -import org.webrtc.Camera2Enumerator; -import org.webrtc.CameraEnumerator; import org.webrtc.DefaultVideoDecoderFactory; import org.webrtc.DefaultVideoEncoderFactory; import org.webrtc.EglBase; -import org.webrtc.Logging; -import org.webrtc.MediaConstraints; import org.webrtc.MediaStreamTrack; import org.webrtc.PeerConnectionFactory; import org.webrtc.PmxFrameCryptor; -import org.webrtc.SurfaceTextureHelper; -import org.webrtc.VideoCapturer; import org.webrtc.VideoDecoderFactory; import org.webrtc.VideoEncoderFactory; -import org.webrtc.VideoSink; -import org.webrtc.VideoSource; import org.webrtc.VideoTrack; import org.webrtc.audio.AudioDeviceModule; import org.webrtc.audio.JavaAudioDeviceModule; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; //TODO: Good to remove context from StreamApi public class StreamApi { @@ -191,7 +171,7 @@ public void leaveStreamRoom(String streamRoomId) { public StreamHandle createStream(String streamRoomId) { RoomJanusSession session = pcManager.getSession(streamRoomId); - if(session == null) throw new IllegalStateException("Session to this room is not exsists. Call joinStreamRoom first"); + if(session == null) throw new IllegalStateException("Session to this room is not exists. Call joinStreamRoom first"); try { session.createPublisher(); }catch (IllegalStateException e){ @@ -203,6 +183,16 @@ public StreamHandle createStream(String streamRoomId) { return handle; } + public TrackFactory getTrackFactory(StreamHandle streamHandle){ + RoomJanusSession session = pcManager.getSession(streamHandle); + if (session == null) + throw new IllegalStateException("Stream not exists. Create stream first."); + JanusPublisher publisher = session.getPublisher(); + if (publisher == null) + throw new IllegalStateException("This StreamHandle has not created companion publisher."); + return new TrackFactory(publisher); + } + /** * @param streamHandle * @param track @@ -220,11 +210,11 @@ public void addTrack( throw new IllegalStateException("This StreamHandle has not created companion publisher."); switch (track.kind()){ case MediaStreamTrack.VIDEO_TRACK_KIND: { - publisher.addAudioTrack((AudioTrack) track); + publisher.addVideoTrack((VideoTrack) track); break; } case MediaStreamTrack.AUDIO_TRACK_KIND:{ - publisher.addVideoTrack((VideoTrack) track); + publisher.addAudioTrack((AudioTrack) track); break; } } diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java new file mode 100644 index 00000000..e4181892 --- /dev/null +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java @@ -0,0 +1,45 @@ +package com.simplito.java.privmx_endpoint.modules.stream; + +import org.webrtc.AudioSource; +import org.webrtc.AudioTrack; +import org.webrtc.MediaConstraints; +import org.webrtc.PeerConnectionFactory; +import org.webrtc.VideoSource; +import org.webrtc.VideoTrack; + +public class TrackFactory { + private final PeerConnectionFactory factory; + TrackFactory(JanusPublisher publisher){ + factory = publisher.peerConnectionFactory; + } + + //TODO: Maybe creating sources should be hidden + public VideoSource createVideoSource(boolean isScreenCast){ + return factory.createVideoSource(isScreenCast); + } + + //TODO: Maybe creating sources should be hidden + public VideoSource createVideoSource(boolean isScreenCast, boolean alignTimestamps){ + return factory.createVideoSource(isScreenCast,alignTimestamps); + } + + //TODO: Maybe creating sources should be hidden + public AudioSource createAudioSource(){ + return factory.createAudioSource(new MediaConstraints()); + } + + public VideoTrack createVideoTrack( + String id, + VideoSource videoSource + ){ + return factory.createVideoTrack(id,videoSource); + } + + public AudioTrack createAudioTrack( + String id, + AudioSource audioSource + ){ + return factory.createAudioTrack(id,audioSource); + } + +} \ No newline at end of file From 557d1a7de495333784012c57e7dc478efc283bc7 Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Wed, 18 Feb 2026 10:59:26 +0100 Subject: [PATCH 3/9] feat: New method for creating tracks --- .../modules/stream/PeerConnectionManager.java | 6 +++--- .../privmx_endpoint/modules/stream/StreamApi.java | 12 ++---------- .../privmx_endpoint/modules/stream/TrackFactory.java | 4 ++-- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java index 39e4b67f..6c179ecc 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java @@ -16,13 +16,13 @@ import java.util.function.BiConsumer; import java.util.function.Function; -public class PeerConnectionManager { +class PeerConnectionManager { private final Map sessions = new HashMap<>(); private final Map sessionHandles = new HashMap<>(); - private final PeerConnectionFactory pcFactory; + protected final PeerConnectionFactory pcFactory; private final BiConsumer onTrickle; - public PeerConnectionManager( + PeerConnectionManager( PeerConnectionFactory pcFactory, BiConsumer onTrickle ) { diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java index 4caa3863..8a6e3e47 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java @@ -44,6 +44,7 @@ public class StreamApi { private final EglBase rootEglBase; private final StreamApiLow api; private final PeerConnectionManager pcManager; + public final TrackFactory trackFactory; private static PeerConnectionFactory DefaultPeerConnectionFactory( Context appContext, @@ -106,6 +107,7 @@ public StreamApi( this.api.trickle(sessionId, rtcConfiguration); } }); + trackFactory = new TrackFactory(pcManager); } public String createStreamRoom( @@ -183,16 +185,6 @@ public StreamHandle createStream(String streamRoomId) { return handle; } - public TrackFactory getTrackFactory(StreamHandle streamHandle){ - RoomJanusSession session = pcManager.getSession(streamHandle); - if (session == null) - throw new IllegalStateException("Stream not exists. Create stream first."); - JanusPublisher publisher = session.getPublisher(); - if (publisher == null) - throw new IllegalStateException("This StreamHandle has not created companion publisher."); - return new TrackFactory(publisher); - } - /** * @param streamHandle * @param track diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java index e4181892..f051f6ea 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackFactory.java @@ -9,8 +9,8 @@ public class TrackFactory { private final PeerConnectionFactory factory; - TrackFactory(JanusPublisher publisher){ - factory = publisher.peerConnectionFactory; + TrackFactory(PeerConnectionManager pcManager){ + factory = pcManager.pcFactory; } //TODO: Maybe creating sources should be hidden From e7c77430a1f21db6b08b8022bf8e61a1aa6004a9 Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Wed, 18 Feb 2026 11:01:41 +0100 Subject: [PATCH 4/9] feat: New logic for observing remote tracks --- .../modules/stream/PcObserver.java | 42 +++----------- .../modules/stream/PeerConnectionManager.java | 4 +- .../modules/stream/RoomJanusSession.java | 57 ++++++++++++++----- .../modules/stream/StreamApi.java | 50 ++++++++++++---- .../modules/stream/TrackObserver.java | 4 +- 5 files changed, 95 insertions(+), 62 deletions(-) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java index 31e3e9fd..11eeca00 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java @@ -20,6 +20,7 @@ import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.stream.Collectors; //TODO: Fix warnings public class PcObserver implements PeerConnection.Observer { @@ -31,17 +32,7 @@ public class PcObserver implements PeerConnection.Observer { private BiConsumer, RtpReceiver> onAddTrack; private Consumer onVideoTrack; - private Consumer onSignalingState; - private Consumer onPeerConnectionState; - private Consumer onIceGatheringState; - private Consumer onIceConnectionState; private Consumer onIceCandidate; - private Consumer onAddStream; - private Consumer onRemoveStream; - private Consumer onDataChannel; - private Runnable onRenegotiationNeeded; - private Consumer onTrack; - private Consumer onRemoveTrack; public PcObserver( PeerConnectionFactory peerConnectionFactory, @@ -55,21 +46,6 @@ public PcObserver( this.onIceCandidate = onIceCandidate; } - public PcObserver( - PeerConnectionFactory peerConnectionFactory, - PmxKeyStore store, - TrackObserver observer - ) { - this(peerConnectionFactory, store, observer,null); - } - - public PcObserver( - PeerConnectionFactory peerConnectionFactory, - PmxKeyStore store - ) { - this(peerConnectionFactory, store, null,null); - } - public void setOnAddTrack(BiConsumer, RtpReceiver> onAddTrack) { this.onAddTrack = onAddTrack; } @@ -109,9 +85,7 @@ public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) { } @Override - public void onAddStream(MediaStream mediaStream) { - - } + public void onAddStream(MediaStream mediaStream) {} @Override public void onRemoveStream(MediaStream mediaStream) { @@ -150,11 +124,13 @@ public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams) { } @Override - public void onTrack(RtpTransceiver transceiver) { - RtpReceiver rtpReceiver = transceiver.getReceiver(); - MediaStreamTrack track = rtpReceiver.track(); - if (trackObserver != null) trackObserver.onTrack(track); - PmxFrameCryptorFactory.createPmxFrameCryptorForRtpReceiver(peerConnectionFactory, rtpReceiver, keyStore); + public void onTrack(RtpTransceiver transceiver) {} + + @Override + public void onRemoveTrack(RtpReceiver receiver) { + //TODO: cleanup track cryptors (?) +// onRemoveTrack.accept(receiver.track()); + receiver.dispose(); } public void setFrameCryptorOptions(PmxFrameCryptor.PmxFrameCryptorOptions options) { diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java index 6c179ecc..212d0114 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PeerConnectionManager.java @@ -31,9 +31,9 @@ class PeerConnectionManager { } @NonNull - public RoomJanusSession createSession(@NonNull String streamRoomId, TrackObserver defaultTrackObserver) { + public RoomJanusSession createSession(@NonNull String streamRoomId) { return Optional.ofNullable( - sessions.putIfAbsent(streamRoomId, new RoomJanusSession(streamRoomId, pcFactory, defaultTrackObserver, onTrickle)) + sessions.putIfAbsent(streamRoomId, new RoomJanusSession(streamRoomId, pcFactory, onTrickle)) ).orElse(Objects.requireNonNull(sessions.get(streamRoomId))); } diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/RoomJanusSession.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/RoomJanusSession.java index 60d3ef08..99246297 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/RoomJanusSession.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/RoomJanusSession.java @@ -15,7 +15,10 @@ import org.webrtc.PmxKeyStore; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BiConsumer; @@ -33,14 +36,14 @@ public class RoomJanusSession { private final PmxKeyStore keyStore; public final WebRTCImpl webrtc = new WebRTCImpl(); private final BiConsumer onTrickle; - private final TrackObserver defaultTrackObserver; + private final Map trackObserversByStreamId = new HashMap<>(); + private final TrackObserver trackObserver = new TrackObserverImpl(); //TODO: Add error listener for catch errors from webrtcInterface - public RoomJanusSession(@NonNull String roomId, @NonNull PeerConnectionFactory pcFactory, TrackObserver defaultTrackObserver, BiConsumer onTrickle) { + public RoomJanusSession(@NonNull String roomId, @NonNull PeerConnectionFactory pcFactory, BiConsumer onTrickle) { this.pcFactory = pcFactory; this.roomID = roomId; this.keyStore = PmxFrameCryptorFactory.createPmxKeyStore(); - this.defaultTrackObserver = defaultTrackObserver; this.onTrickle = onTrickle; } @@ -55,7 +58,7 @@ public synchronized JanusPublisher getPublisher() { } public synchronized void createSubscriber() { - createSubscriber(defaultTrackObserver); + createSubscriber(trackObserver); } public synchronized void createSubscriber(TrackObserver observer) { @@ -70,7 +73,7 @@ public synchronized void createSubscriber(TrackObserver observer) { } public synchronized void createPublisher() { - createPublisher(defaultTrackObserver); + createPublisher(null); } public synchronized void createPublisher(TrackObserver observer) { @@ -84,6 +87,30 @@ public synchronized void createPublisher(TrackObserver observer) { } } + public void setTrackObserver( + TrackObserver trackObserver + ){ + setTrackObserver(null,trackObserver); + } + + public void setTrackObserver( + String streamId, + TrackObserver trackObserver + ){ + synchronized (trackObserversByStreamId) { + trackObserversByStreamId.put(streamId, trackObserver); + } + } + + public void setFrameCryptorOptions(PmxFrameCryptor.PmxFrameCryptorOptions options) { + if(subscriber != null){ + subscriber.setFrameCryptorOptions(options); + } + if(publisher != null){ + publisher.setFrameCryptorOptions(options); + } + } + public class WebRTCImpl implements WebRTCInterface { private final ExecutorService executor = Executors.newSingleThreadExecutor(); @@ -171,14 +198,18 @@ public void updateSessionId(String streamRoomId, Long sessionId, String connecti } } } - - - public void setFrameCryptorOptions(PmxFrameCryptor.PmxFrameCryptorOptions options) { - if(subscriber != null){ - subscriber.setFrameCryptorOptions(options); - } - if(publisher != null){ - publisher.setFrameCryptorOptions(options); + private class TrackObserverImpl implements TrackObserver{ + @Override + public void OnRemoteTrack(String streamId, MediaStreamTrack track) { + synchronized (trackObserversByStreamId){ + Optional.ofNullable(trackObserversByStreamId.get(streamId)).ifPresent(observer->{ + observer.OnRemoteTrack(streamId,track); + }); + + Optional.ofNullable(trackObserversByStreamId.get(null)).ifPresent(observer->{ + observer.OnRemoteTrack(streamId,track); + }); + } } } } diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java index 8a6e3e47..94511fb1 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java @@ -31,7 +31,10 @@ import org.webrtc.audio.AudioDeviceModule; import org.webrtc.audio.JavaAudioDeviceModule; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; //TODO: Good to remove context from StreamApi public class StreamApi { @@ -102,8 +105,8 @@ public StreamApi( } pcManager = new PeerConnectionManager( factory, - (sessionId, rtcConfiguration)->{ - if(sessionId != null) { + (sessionId, rtcConfiguration) -> { + if (sessionId != null) { this.api.trickle(sessionId, rtcConfiguration); } }); @@ -159,10 +162,10 @@ public List listStreams(String streamRoomId) { } public void joinStreamRoom( - String streamRoomId, - TrackObserver trackObserver + String streamRoomId ) { - RoomJanusSession session = pcManager.createSession(streamRoomId,trackObserver); + //TODO: Rollback this change, it is do only for run test + RoomJanusSession session = pcManager.createSession(streamRoomId); api.joinStreamRoom(streamRoomId, session.webrtc); } @@ -173,10 +176,11 @@ public void leaveStreamRoom(String streamRoomId) { public StreamHandle createStream(String streamRoomId) { RoomJanusSession session = pcManager.getSession(streamRoomId); - if(session == null) throw new IllegalStateException("Session to this room is not exists. Call joinStreamRoom first"); + if (session == null) + throw new IllegalStateException("Session to this room is not exists. Call joinStreamRoom first"); try { session.createPublisher(); - }catch (IllegalStateException e){ + } catch (IllegalStateException e) { throw new IllegalStateException("Publisher is now active, try use modifyRemoteStreamsSubscriptions"); } @@ -194,24 +198,44 @@ public void addTrack( StreamHandle streamHandle, MediaStreamTrack track ) throws IllegalStateException { + Objects.requireNonNull(streamHandle); RoomJanusSession session = pcManager.getSession(streamHandle); if (session == null) throw new IllegalStateException("Stream not exists. Create stream first."); JanusPublisher publisher = session.getPublisher(); if (publisher == null) throw new IllegalStateException("This StreamHandle has not created companion publisher."); - switch (track.kind()){ + switch (track.kind()) { case MediaStreamTrack.VIDEO_TRACK_KIND: { publisher.addVideoTrack((VideoTrack) track); break; } - case MediaStreamTrack.AUDIO_TRACK_KIND:{ + case MediaStreamTrack.AUDIO_TRACK_KIND: { publisher.addAudioTrack((AudioTrack) track); break; } } } + public void setTrackObserver( + String roomId, + TrackObserver observer, + String streamId + ) { + Objects.requireNonNull(roomId); + RoomJanusSession session = pcManager.getSession(roomId); + if (session == null) + throw new IllegalStateException("Session to this room is not exists. Call joinStreamRoom first."); + session.setTrackObserver(streamId, observer); + } + + public void setTrackObserver( + String roomId, + TrackObserver observer + ) { + setTrackObserver(roomId, observer, null); + } + /** * @param streamHandle * @param track @@ -221,6 +245,7 @@ public void removeTrack( StreamHandle streamHandle, MediaStreamTrack track ) throws IllegalStateException { + Objects.requireNonNull(streamHandle); RoomJanusSession session = pcManager.getSession(streamHandle); if (session == null) throw new IllegalStateException("Stream with this StreamHandle doesn't exist."); @@ -235,14 +260,17 @@ public void removeTrack( } public StreamPublishResult publishStream(StreamHandle streamHandle) { + Objects.requireNonNull(streamHandle); return api.publishStream(streamHandle); } public StreamPublishResult updateStream(StreamHandle streamHandle) { + Objects.requireNonNull(streamHandle); return api.updateStream(streamHandle); } public void unpublishStream(StreamHandle streamHandle) { + Objects.requireNonNull(streamHandle); api.unpublishStream(streamHandle); } @@ -263,7 +291,7 @@ public void subscribeToRemoteStreams( throw new IllegalStateException("No active session to this Stream Room. Join stream room first"); try { session.createSubscriber(); - }catch (IllegalStateException e){ + } catch (IllegalStateException e) { throw new IllegalStateException("Subscriber is now active, try use modifyRemoteStreamsSubscriptions"); } api.subscribeToRemoteStreams(streamRoomId, subscriptions, options); @@ -311,7 +339,7 @@ public void dropBrokenFrames( boolean enable ) { RoomJanusSession session = pcManager.getSession(streamRoomId); - if(session != null){ + if (session != null) { PmxFrameCryptor.PmxFrameCryptorOptions options = new PmxFrameCryptor.PmxFrameCryptorOptions(); options.dropFrameIfCryptionFailed = enable; session.setFrameCryptorOptions(options); diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackObserver.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackObserver.java index 5ab019ee..8de6eba2 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackObserver.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/TrackObserver.java @@ -3,7 +3,5 @@ import org.webrtc.MediaStreamTrack; public interface TrackObserver { - void onTrack(MediaStreamTrack track); - void onVideoTrack(String trackId); - void onRemoveVideoTrack(String trackId); + void OnRemoteTrack(String streamId, MediaStreamTrack track); } From ef863242f30526b25bd21331379a5225185a2033 Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Wed, 18 Feb 2026 11:04:41 +0100 Subject: [PATCH 5/9] fix: Add callback execution --- .../java/privmx_endpoint/modules/stream/PcObserver.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java index 11eeca00..1326fb45 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java @@ -114,12 +114,8 @@ public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams) { ) ); } - if (onAddTrack != null) { - onAddTrack.accept(Arrays.asList(mediaStreams), receiver); - - if (Objects.equals(receiver.track().kind(), MediaStreamTrack.VIDEO_TRACK_KIND)) { - onVideoTrack.accept(receiver.track().id()); - } + if (trackObserver != null) { + trackObserver.OnRemoteTrack(mediaStreams[0].getId(),receiver.track()); } } From 7189a44edbe1ce66d3297d24f46bb220d6ba2008 Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Wed, 18 Feb 2026 11:44:13 +0100 Subject: [PATCH 6/9] fix: setup framecryptorfactory in correct track state --- .../modules/stream/PcObserver.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java index 1326fb45..1b19b9ba 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java @@ -104,24 +104,28 @@ public void onRenegotiationNeeded() { @Override public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams) { - if (peerConnectionFactory != null && receiver.track() != null && receiver.track().id().equals(null)) { + if (trackObserver != null) { + trackObserver.OnRemoteTrack(mediaStreams[0].getId(),receiver.track()); + } + } + + @Override + public void onTrack(RtpTransceiver transceiver) { + RtpReceiver rtpReceiver = transceiver.getReceiver(); + if (peerConnectionFactory != null && rtpReceiver.track() != null && rtpReceiver.track().id() != null) { + + PmxFrameCryptorFactory.createPmxFrameCryptorForRtpReceiver(peerConnectionFactory, rtpReceiver, keyStore); frameCryptorMap.put( - receiver.track().id(), + rtpReceiver.track().id(), PmxFrameCryptorFactory.createPmxFrameCryptorForRtpReceiver( peerConnectionFactory, - receiver, + rtpReceiver, keyStore ) ); } - if (trackObserver != null) { - trackObserver.OnRemoteTrack(mediaStreams[0].getId(),receiver.track()); - } } - @Override - public void onTrack(RtpTransceiver transceiver) {} - @Override public void onRemoveTrack(RtpReceiver receiver) { //TODO: cleanup track cryptors (?) From 10c6ace12043134c387003d4940d28a6e1b011d6 Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Thu, 19 Feb 2026 23:47:27 +0100 Subject: [PATCH 7/9] fix: Move onRemoteTrack callback execution from onAddTrack to onTrack --- .../modules/stream/PcObserver.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java index 1b19b9ba..c5073810 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java @@ -33,6 +33,7 @@ public class PcObserver implements PeerConnection.Observer { private Consumer onVideoTrack; private Consumer onIceCandidate; + private final Map streamIdsByTracks = new HashMap<>(); public PcObserver( PeerConnectionFactory peerConnectionFactory, @@ -104,25 +105,33 @@ public void onRenegotiationNeeded() { @Override public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams) { - if (trackObserver != null) { - trackObserver.OnRemoteTrack(mediaStreams[0].getId(),receiver.track()); + MediaStreamTrack track = receiver.track(); + if(track != null && mediaStreams.length > 0) { + streamIdsByTracks.put(track.id(), mediaStreams[0].getId()); } } @Override public void onTrack(RtpTransceiver transceiver) { RtpReceiver rtpReceiver = transceiver.getReceiver(); - if (peerConnectionFactory != null && rtpReceiver.track() != null && rtpReceiver.track().id() != null) { + MediaStreamTrack track = rtpReceiver.track(); + if (peerConnectionFactory != null && track != null && track.id() != null) { PmxFrameCryptorFactory.createPmxFrameCryptorForRtpReceiver(peerConnectionFactory, rtpReceiver, keyStore); frameCryptorMap.put( - rtpReceiver.track().id(), + track.id(), PmxFrameCryptorFactory.createPmxFrameCryptorForRtpReceiver( peerConnectionFactory, rtpReceiver, keyStore ) ); + if(trackObserver != null){ + String streamId = streamIdsByTracks.get(track.id()); + if(streamId != null) { + trackObserver.OnRemoteTrack(streamId, track); + } + } } } From 46f2e4ac4f3b7a53c6f48ae61527783308bd7d1b Mon Sep 17 00:00:00 2001 From: Dawid Jenczewski Date: Thu, 19 Feb 2026 23:47:49 +0100 Subject: [PATCH 8/9] chore: Import missing class --- .../simplito/java/privmx_endpoint/modules/stream/StreamApi.java | 1 + 1 file changed, 1 insertion(+) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java index 793780b0..94511fb1 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApi.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; //TODO: Good to remove context from StreamApi From 119071ea84391b2be25f439f9d92596141dc7826 Mon Sep 17 00:00:00 2001 From: djenczewski <149203207+djenczewski@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:02:55 +0100 Subject: [PATCH 9/9] remove duplicated createPmxFrameCryptorForRtpReceiver method call --- .../java/privmx_endpoint/modules/stream/PcObserver.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java index c5073810..c422b13c 100644 --- a/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java +++ b/privmx-endpoint-streams/android/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/PcObserver.java @@ -116,8 +116,6 @@ public void onTrack(RtpTransceiver transceiver) { RtpReceiver rtpReceiver = transceiver.getReceiver(); MediaStreamTrack track = rtpReceiver.track(); if (peerConnectionFactory != null && track != null && track.id() != null) { - - PmxFrameCryptorFactory.createPmxFrameCryptorForRtpReceiver(peerConnectionFactory, rtpReceiver, keyStore); frameCryptorMap.put( track.id(), PmxFrameCryptorFactory.createPmxFrameCryptorForRtpReceiver(