diff --git a/jni-wrappers/privmx-endpoint/src/cpp/modules/StreamApiLow.cpp b/jni-wrappers/privmx-endpoint/src/cpp/modules/StreamApiLow.cpp index 10da9904..546c8d23 100644 --- a/jni-wrappers/privmx-endpoint/src/cpp/modules/StreamApiLow.cpp +++ b/jni-wrappers/privmx-endpoint/src/cpp/modules/StreamApiLow.cpp @@ -665,19 +665,17 @@ Java_com_simplito_java_privmx_1endpoint_modules_stream_StreamApiLow_modifyRemote jobject thiz, jstring stream_room_id, jobject subscriptions_to_add, - jobject subscriptions_to_remove, - jobject options + jobject subscriptions_to_remove ) { JniContextUtils ctx(env); if (ctx.nullCheck(stream_room_id, "Stream Room ID") || ctx.nullCheck(subscriptions_to_add, "Subscriptions to add") || - ctx.nullCheck(subscriptions_to_add, "Subscriptions to remove") || - ctx.nullCheck(options, "Options")) { + ctx.nullCheck(subscriptions_to_add, "Subscriptions to remove")) { return; } ctx.callVoidEndpointApi( - [&ctx, &thiz, &stream_room_id, &subscriptions_to_add, &subscriptions_to_remove, &options]() { + [&ctx, &thiz, &stream_room_id, &subscriptions_to_add, &subscriptions_to_remove]() { auto subscriptions_to_add_arr = ctx.jObject2jArray(subscriptions_to_remove); auto subscriptions_to_remove_arr = ctx.jObject2jArray(subscriptions_to_remove); @@ -708,16 +706,14 @@ Java_com_simplito_java_privmx_1endpoint_modules_stream_StreamApiLow_subscribeToR JNIEnv *env, jobject thiz, jstring stream_room_id, - jobject subscriptions, - jobject options -) { + jobject subscriptions + ) { JniContextUtils ctx(env); if (ctx.nullCheck(stream_room_id, "Stream Room ID") || - ctx.nullCheck(subscriptions, "Subscriptions") || - ctx.nullCheck(options, "Options")) { + ctx.nullCheck(subscriptions, "Subscriptions")) { return; } - ctx.callVoidEndpointApi([&ctx, &thiz, &stream_room_id, &subscriptions, &options]() { + ctx.callVoidEndpointApi([&ctx, &thiz, &stream_room_id, &subscriptions]() { auto subscriptions_arr = ctx.jObject2jArray(subscriptions); auto subscriptions_c = jArrayToVector( ctx, 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 87767fc2..5ffb6f18 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 @@ -9,7 +9,6 @@ import com.simplito.java.privmx_endpoint.model.ContainerPolicy; import com.simplito.java.privmx_endpoint.model.PagingList; import com.simplito.java.privmx_endpoint.model.UserWithPubKey; -import com.simplito.java.privmx_endpoint.model.stream.Settings; import com.simplito.java.privmx_endpoint.model.stream.StreamHandle; import com.simplito.java.privmx_endpoint.model.stream.StreamInfo; import com.simplito.java.privmx_endpoint.model.stream.StreamPublishResult; @@ -37,9 +36,19 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -public class StreamApi implements AutoCloseable{ +/** + * High-level API for managing PrivMX Stream Rooms and WebRTC media sessions on Android. + *

+ * {@code StreamApi} is a high-level wrapper over {@link StreamApiLow}, providing + * a simplified interface for working with Stream Rooms. + */ +public class StreamApi implements AutoCloseable { private final StreamApiLow api; private final PeerConnectionManager pcManager; + + /** + * Factory which provides helpers for creating WebRTC media sources and tracks. + */ public final TrackFactory trackFactory; private static PeerConnectionFactory DefaultPeerConnectionFactory( @@ -74,6 +83,13 @@ private static PeerConnectionFactory DefaultPeerConnectionFactory( return factory; } + /** + * Creates a {@code StreamApi} instance. + * + * @param appContext Android application context + * @param rootEglBase {@link EglBase} context used for hardware-accelerated video encoding and decoding + * @param api initialised {@link StreamApiLow} instance + */ public StreamApi( @NonNull Context appContext, @NonNull EglBase rootEglBase, @@ -96,6 +112,19 @@ public StreamApi( trackFactory = new TrackFactory(pcManager); } + /** + * Creates a new Stream Room within the specified Context. + * + * @param contextId ID of the Context to create the Stream Room in + * @param users list of {@link UserWithPubKey} indicating which users will have + * access to the created Stream Room + * @param managers list of {@link UserWithPubKey} indicating which users will have + * access and management rights to the created Stream Room + * @param publicMeta public (unencrypted) metadata + * @param privateMeta private (encrypted) metadata + * @param policies additional container access policies, or {@code null} to use default settings + * @return ID of the created Stream Room + */ public String createStreamRoom( String contextId, List users, @@ -107,6 +136,23 @@ public String createStreamRoom( return api.createStreamRoom(contextId, users, managers, publicMeta, privateMeta, policies); } + /** + * Updates an existing Stream Room. + * + * @param streamRoomId ID of the Stream Room to update + * @param users list of {@link UserWithPubKey} indicating which users will have + * access to the created Stream Room + * @param managers list of {@link UserWithPubKey} indicating which users will have + * access and management rights to the created Stream Room + * @param publicMeta public (unencrypted) metadata + * @param privateMeta private (encrypted) metadata + * @param version current version of the updated Stream Room + * @param force force update (without checking version) + * @param forceGenerateNewKey force to regenerate the encryption key for the Stream Room. + * Should be {@code true} whenever a user is removed + * @param policies additional container access policies, or {@code null} to keep + * current/defaults + */ public void updateStreamRoom( String streamRoomId, List users, @@ -121,6 +167,19 @@ public void updateStreamRoom( api.updateStreamRoom(streamRoomId, users, managers, publicMeta, privateMeta, version, force, forceGenerateNewKey, policies); } + /** + * Gets a list of Stream Rooms in given Context. + * + * @param contextId ID of the Context to get Stream Rooms from + * @param skip number of elements to skip from result + * @param limit limit of elements to return for query + * @param sortOrder order of elements in result ({@code "asc"} for ascending, + * {@code "desc"} for descending) + * @param lastId ID of the element from which query results should start + * @param sortBy field name to sort elements by + * @param queryAsJson stringified JSON object with a custom field to filter result + * @return list of Stream Rooms + */ public PagingList listStreamRooms( String contextId, long skip, @@ -133,6 +192,18 @@ public PagingList listStreamRooms( return api.listStreamRooms(contextId, skip, limit, sortOrder, lastId, sortBy, queryAsJson); } + /** + * Gets a list of Stream Rooms in given Context. + * + * @param contextId ID of the Context to get Stream Rooms from + * @param skip number of elements to skip from result + * @param limit limit of elements to return for query + * @param sortOrder order of elements in result ({@code "asc"} for ascending, + * {@code "desc"} for descending) + * @param lastId ID of the element from which query results should start + * @param sortBy field name to sort elements by + * @return list of Stream Rooms + */ public PagingList listStreamRooms( String contextId, long skip, @@ -144,6 +215,17 @@ public PagingList listStreamRooms( return listStreamRooms(contextId, skip, limit, sortOrder, lastId, sortBy, null); } + /** + * Gets a list of Stream Rooms in given Context. + * + * @param contextId ID of the Context to get Stream Rooms from + * @param skip number of elements to skip from result + * @param limit limit of elements to return for query + * @param sortOrder order of elements in result ({@code "asc"} for ascending, + * {@code "desc"} for descending) + * @param lastId ID of the element from which query results should start + * @return list of Stream Rooms + */ public PagingList listStreamRooms( String contextId, long skip, @@ -154,6 +236,16 @@ public PagingList listStreamRooms( return listStreamRooms(contextId, skip, limit, sortOrder, lastId, null, null); } + /** + * Gets a list of Stream Rooms in given Context. + * + * @param contextId ID of the Context to get Stream Rooms from + * @param skip number of elements to skip from result + * @param limit limit of elements to return for query + * @param sortOrder order of elements in result ({@code "asc"} for ascending, + * {@code "desc"} for descending) + * @return list of Stream Rooms + */ public PagingList listStreamRooms( String contextId, long skip, @@ -163,19 +255,44 @@ public PagingList listStreamRooms( return listStreamRooms(contextId, skip, limit, sortOrder, null, null, null); } - + /** + * Gets a single Stream Room by given Stream Room ID. + * + * @param streamRoomId ID of the Stream Room to get + * @return struct containing information about the Stream Room + */ public StreamRoom getStreamRoom(String streamRoomId) { return api.getStreamRoom(streamRoomId); } + /** + * Deletes a Stream Room by given Stream Room ID. + * + * @param streamRoomId ID of the Stream Room to delete + */ public void deleteStreamRoom(String streamRoomId) { api.deleteStreamRoom(streamRoomId); } + /** + * Gets a list of currently published streams in given Stream Room. + * + * @param streamRoomId ID of the Stream Room to list streams from + * @return list of {@link StreamInfo} structs describing currently published streams + */ public List listStreams(String streamRoomId) { return api.listStreams(streamRoomId); } + /** + * Joins a Stream Room and establishes a WebRTC session. + *

+ * Must be called before {@link #createStream(String)}, + * {@link #publishStream(StreamHandle)}, and any remote stream subscription calls + * for the given room. + * + * @param streamRoomId ID of the Stream Room to join + */ public void joinStreamRoom( String streamRoomId ) { @@ -183,11 +300,26 @@ public void joinStreamRoom( api.joinStreamRoom(streamRoomId, session.webrtc); } + /** + * Leaves a Stream Room and releases the associated WebRTC session. + * + * @param streamRoomId ID of the Stream Room to leave + */ public void leaveStreamRoom(String streamRoomId) { pcManager.leaveStreamRoom(streamRoomId); api.leaveStreamRoom(streamRoomId); } + /** + * Creates a local stream handle for publishing media in given Stream Room. + *

+ * {@link #joinStreamRoom(String)} must be called before this method. + * Use {@link #addTrack(StreamHandle, MediaStreamTrack)} to add media tracks + * before calling {@link #publishStream(StreamHandle)}. + * + * @param streamRoomId ID of the Stream Room to create the stream in + * @return handle to the local stream instance + */ public StreamHandle createStream(String streamRoomId) { RoomJanusSession session = pcManager.getSession(streamRoomId); if (session == null) @@ -204,8 +336,11 @@ public StreamHandle createStream(String streamRoomId) { } /** - * @param streamHandle - * @param track + * Adds a local media track to a Stream handle. + * The track is staged locally and becomes visible to others after publishStream/updateStream. + * + * @param streamHandle handle returned by {@link #createStream(String)} + * @param track {@link VideoTrack} or {@link AudioTrack} to add * @throws IllegalStateException if call addTrack before call createStream */ public void addTrack( @@ -231,6 +366,17 @@ public void addTrack( } } + /** + * Registers a {@link TrackObserver} to receive callbacks when remote media tracks + * become available for a specific stream in the given Stream Room. + *

+ * Use this method to observe tracks only from a selected remote stream. + * + * @param roomId ID of the Stream Room + * @param observer observer implementation receiving track callbacks + * @param streamId ID of a specific remote stream to observe, or {@code null} for all streams + * @throws IllegalStateException thrown when no active session exists for the given room. + */ public void setTrackObserver( @NonNull String roomId, TrackObserver observer, @@ -243,6 +389,28 @@ public void setTrackObserver( session.setTrackObserver(streamId, observer); } + /** + * Registers a {@link TrackObserver} to receive callbacks when remote media tracks + * become available for all streams in the given Stream Room. + * + * @param roomId ID of the Stream Room + * @param observer observer implementation receiving track callbacks + * @throws IllegalStateException thrown when no active session exists for the given room. + */ + public void setTrackObserver( + @NonNull String roomId, + TrackObserver observer + ) { + setTrackObserver(roomId, observer, null); + } + + /** + * Registers an observer to receive ICE connection state changes for the given Stream Room. + * + * @param roomId ID of the Stream Room + * @param observer callback receiving {@link PeerConnection.IceConnectionState} values + * @throws IllegalStateException thrown when no active session exists for the given room. + */ public void setConnectionStateObserver( @NonNull String roomId, Consumer observer @@ -254,17 +422,16 @@ public void setConnectionStateObserver( session.setOnConnectionChange(observer); } - public void setTrackObserver( - String roomId, - TrackObserver observer - ) { - setTrackObserver(roomId, observer, null); - } /** - * @param streamHandle - * @param track - * @throws IllegalStateException when Stream with this StreamHandle doesn't exist. + * Removes a media track from a stream. + *

+ * After removing tracks, call {@link #updateStream(StreamHandle)} to propagate + * the change to other participants. + * + * @param streamHandle handle returned by {@link #createStream(String)} + * @param track {@link VideoTrack} or {@link AudioTrack} to remove + * @throws IllegalStateException thrown when Stream with this StreamHandle doesn't exist. */ public void removeTrack( @NonNull StreamHandle streamHandle, @@ -284,6 +451,14 @@ public void removeTrack( } } + /** + * Publishes the stream (with currently added tracks) to the server, + * making it visible to other participants in the room. + * + * @param streamHandle handle returned by {@link #createStream(String)} + * @return result of the publish operation containing session information + * @throws IllegalStateException thrown when no stream exists for the given handle. + */ public StreamPublishResult publishStream(@NonNull StreamHandle streamHandle) { Objects.requireNonNull(streamHandle); RoomJanusSession session = pcManager.getSession(streamHandle); @@ -296,6 +471,17 @@ public StreamPublishResult publishStream(@NonNull StreamHandle streamHandle) { return api.publishStream(streamHandle); } + /** + * Updates a published stream after track changes. + *

+ * Call this after {@link #addTrack(StreamHandle, MediaStreamTrack)} or + * {@link #removeTrack(StreamHandle, MediaStreamTrack)} on an already-published stream + * to propagate the changes to other participants. + * + * @param streamHandle handle returned by {@link #createStream(String)} + * @return result of the update operation containing updated session information + * @throws IllegalStateException thrown when no stream exists for the given handle. + */ public StreamPublishResult updateStream(@NonNull StreamHandle streamHandle) { Objects.requireNonNull(streamHandle); RoomJanusSession session = pcManager.getSession(streamHandle); @@ -308,22 +494,30 @@ public StreamPublishResult updateStream(@NonNull StreamHandle streamHandle) { return api.updateStream(streamHandle); } + /** + * Stops publishing the stream. + * + * @param streamHandle handle returned by {@link #createStream(String)} + * @throws IllegalStateException thrown when instance is closed. + */ public void unpublishStream(@NonNull StreamHandle streamHandle) { Objects.requireNonNull(streamHandle); api.unpublishStream(streamHandle); } + /** + * Subscribes to selected remote streams in a Stream Room. + *

+ * {@link #joinStreamRoom(String)} must be called before this method. + * + * @param streamRoomId ID of the Stream Room + * @param subscriptions list of {@link StreamSubscription} structs describing the remote + * streams to subscribe to + * @throws IllegalStateException thrown when no active session exists for the given room. + */ public void subscribeToRemoteStreams( String streamRoomId, List subscriptions - ) { - subscribeToRemoteStreams(streamRoomId, subscriptions, new Settings()); - } - - public void subscribeToRemoteStreams( - String streamRoomId, - List subscriptions, - Settings options ) { RoomJanusSession session = pcManager.getSession(streamRoomId); if (session == null) @@ -335,27 +529,26 @@ public void subscribeToRemoteStreams( if (session.getSubscriber() == null) throw new IllegalStateException("This streamRoom has not created companion subscriber."); session.getSubscriber().setRTCConfiguration(getRTCConfiguration()); - api.subscribeToRemoteStreams(streamRoomId, subscriptions, options); + api.subscribeToRemoteStreams(streamRoomId, subscriptions); } - public void modifyRemoteStreamsSubscriptions( - String streamRoomId, - List subscriptionsToAdd, - List subscriptionsToRemove - ) { - modifyRemoteStreamsSubscriptions( - streamRoomId, - subscriptionsToAdd, - subscriptionsToRemove, - new Settings() - ); - } + /** + * Modifies the current list of remote stream subscriptions in a Stream Room. + *

+ * Allows atomically adding and removing remote stream subscriptions in a single call, + * avoiding the need to fully unsubscribe and resubscribe. + * + * @param streamRoomId ID of the Stream Room + * @param subscriptionsToAdd list of {@link StreamSubscription} structs to add + * @param subscriptionsToRemove list of {@link StreamSubscription} structs to remove + * @throws IllegalStateException thrown when no active session or subscriber exists + * for the given room. + */ public void modifyRemoteStreamsSubscriptions( String streamRoomId, List subscriptionsToAdd, - List subscriptionsToRemove, - Settings options + List subscriptionsToRemove ) { RoomJanusSession session = pcManager.getSession(streamRoomId); if (session == null) @@ -366,11 +559,18 @@ public void modifyRemoteStreamsSubscriptions( api.modifyRemoteStreamsSubscriptions( streamRoomId, subscriptionsToAdd, - subscriptionsToRemove, - options + subscriptionsToRemove ); } + /** + * Unsubscribes from selected remote streams in a Stream Room. + * + * @param streamRoomId ID of the Stream Room + * @param subscriptionsToRemove list of {@link StreamSubscription} structs to remove + * @throws IllegalStateException thrown when no active session or subscriber exists + * for the given room. + */ public void unsubscribeFromRemoteStreams( String streamRoomId, List subscriptionsToRemove @@ -387,6 +587,16 @@ public void unsubscribeFromRemoteStreams( ); } + /** + * Controls whether encrypted media frames that cannot be decrypted should be dropped. + *

+ * When enabled, this prevents corrupted audio or video from being rendered in situations + * such as key rotation, where a participant may temporarily use an outdated key. + * + * @param streamRoomId ID of the Stream Room + * @param enable {@code true} to silently drop undecryptable frames; + * {@code false} to pass them through unchanged + */ public void dropBrokenFrames( String streamRoomId, boolean enable @@ -399,14 +609,42 @@ public void dropBrokenFrames( } } + /** + * Subscribes to events for the Stream Room as well as its individual streams, + * based on the provided subscription queries. + * + * @param subscriptionQueries list of queries built with + * {@link #buildSubscriptionQuery(StreamEventType, StreamEventSelectorType, String)} + * @return list of subscription IDs in matching order to {@code subscriptionQueries} + * @throws IllegalStateException thrown when instance is closed. + */ public List subscribeFor(List subscriptionQueries) { return api.subscribeFor(subscriptionQueries); } + /** + * Unsubscribes from events with the given subscription IDs. + * + * @param subscriptionIds list of subscription IDs returned by {@link #subscribeFor(List)} + * @throws IllegalStateException thrown when instance is closed. + */ public void unsubscribeFrom(List subscriptionIds) { api.unsubscribeFrom(subscriptionIds); } + /** + * Generates a subscription query string for events for the Stream Room + * as well as its individual streams. + *

+ * The returned query should be passed to {@link #subscribeFor(List)} to start + * receiving the requested events. + * + * @param eventType type of event to listen for + * @param selectorType scope at which events are observed + * @param selectorId ID of the selected entity + * @return query string used for event subscription + * @throws IllegalStateException thrown when instance is closed + */ public String buildSubscriptionQuery( StreamEventType eventType, StreamEventSelectorType selectorType, @@ -419,6 +657,14 @@ public String buildSubscriptionQuery( ); } + /** + * Releases all resources associated with this instance. + *

+ * Leaves all active Stream Rooms, shuts down the peer connection manager, + * and closes the underlying {@link StreamApiLow} instance. + * + * @throws Exception thrown if an error occurs during cleanup + */ @Override public void close() throws Exception { pcManager.getRoomIds().forEach(this::leaveStreamRoom); 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 9d102ebd..af555bcf 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 @@ -2,24 +2,64 @@ import org.webrtc.*; +/** + * Factory for creating WebRTC media sources and tracks. + * Provides methods for creating the audio and video objects needed + * to publish a stream via {@link StreamApi}. + *

+ * You do not create instances of this class directly — obtain the shared instance + * from {@link StreamApi#trackFactory} after constructing a {@link StreamApi}. + */ public class TrackFactory { private final PeerConnectionFactory factory; TrackFactory(PeerConnectionManager pcManager){ factory = pcManager.pcFactory; } + /** + * Creates a {@link VideoSource} that can capture a camera or screen feed. + * + * @param isScreenCast {@code true} if the source is capturing a screen share; + * {@code false} for a regular camera feed. + * @return a new {@link VideoSource} instance + */ public VideoSource createVideoSource(boolean isScreenCast){ return factory.createVideoSource(isScreenCast); } + /** + * Creates a {@link VideoSource} with explicit timestamp alignment control. + * + * @param isScreenCast {@code true} if the source is capturing a screen share; + * {@code false} for a regular camera feed + * @param alignTimestamps {@code true} to align video frame timestamps with the + * audio clock, which can improve A/V sync in some scenarios + * @return a new {@link VideoSource} instance + */ public VideoSource createVideoSource(boolean isScreenCast, boolean alignTimestamps){ return factory.createVideoSource(isScreenCast,alignTimestamps); } + /** + * Creates an {@link AudioSource} with default media constraints. + *

+ * The source captures audio from the device microphone. Default constraints + * enable standard WebRTC audio processing (echo cancellation, noise suppression, + * auto gain control). + * + * @return a new {@link AudioSource} instance + */ public AudioSource createAudioSource(){ return factory.createAudioSource(new MediaConstraints()); } + /** + * Creates a {@link VideoTrack} using the provided {@link VideoSource}. + * @param id unique identifier for this track within the peer connection + * @param videoSource source of video frames + * + * @return a new {@link VideoTrack} instance + */ public VideoTrack createVideoTrack( String id, VideoSource videoSource @@ -27,6 +67,14 @@ public VideoTrack createVideoTrack( return factory.createVideoTrack(id,videoSource); } + /** + * Creates an {@link AudioTrack} using the provided {@link AudioSource}. + * + * @param id unique identifier for this track within the peer connection + * @param audioSource source providing audio samples + * + * @return a new {@link AudioTrack} instance + */ public AudioTrack createAudioTrack( String id, AudioSource audioSource diff --git a/privmx-endpoint/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApiLow.java b/privmx-endpoint/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApiLow.java index c8aeec4d..c6170da2 100644 --- a/privmx-endpoint/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApiLow.java +++ b/privmx-endpoint/src/main/java/com/simplito/java/privmx_endpoint/modules/stream/StreamApiLow.java @@ -15,7 +15,6 @@ import com.simplito.java.privmx_endpoint.model.PagingList; import com.simplito.java.privmx_endpoint.model.UserWithPubKey; import com.simplito.java.privmx_endpoint.model.stream.SdpWithTypeModel; -import com.simplito.java.privmx_endpoint.model.stream.Settings; import com.simplito.java.privmx_endpoint.model.stream.StreamEncryptionMode; import com.simplito.java.privmx_endpoint.model.stream.StreamHandle; import com.simplito.java.privmx_endpoint.model.stream.StreamInfo; @@ -204,9 +203,9 @@ public native StreamHandle createStream( public native void unpublishStream(StreamHandle streamHandle); - public native void subscribeToRemoteStreams(String streamRoomId, List subscriptions, Settings options); + public native void subscribeToRemoteStreams(String streamRoomId, List subscriptions); - public native void modifyRemoteStreamsSubscriptions(String streamRoomId, List subscriptionsToAdd, List subscriptionsToRemove, Settings options); + public native void modifyRemoteStreamsSubscriptions(String streamRoomId, List subscriptionsToAdd, List subscriptionsToRemove); public native void unsubscribeFromRemoteStreams(String streamRoomId, List subscriptionsToRemove);