diff --git a/.gitignore b/.gitignore index 4d4f7c29..a577e359 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ local.properties !.cargo/audit.toml target .spr.yml +src/android/.settings diff --git a/src/android/api/org/signal/ringrtc/CallManager.java b/src/android/api/org/signal/ringrtc/CallManager.java index 4c1592ea..1a40d59c 100644 --- a/src/android/api/org/signal/ringrtc/CallManager.java +++ b/src/android/api/org/signal/ringrtc/CallManager.java @@ -401,7 +401,10 @@ public class CallManager { /** * - * Indication from application to proceed with call + * Indication from application to proceed with call. Defaults call to: + * - videoEnabled == false + * - audioEnabled == false + * - isScreenshare == false * * @param callId callId for the call * @param context Call service context @@ -832,17 +835,39 @@ public class CallManager { * @throws CallException for native code failures * */ - public void setVideoEnable(boolean enable) + public void setVideoEnable(boolean enable, boolean isScreenShare) throws CallException { checkCallManagerExists(); CallContext callContext = ringrtcGetActiveCallContext(nativeCallManager); callContext.setVideoEnabled(enable); + callContext.setOutgoingVideoIsScreenShare(isScreenShare); ringrtcSetVideoEnable(nativeCallManager, enable); } + /** + * + * Notification from application to indicate whether the outgoing video + * is a screen share. + * + * @param isScreenShare if true, the outgoing video is a screen share + * + * @throws CallException for native code failures + * + */ + public void setOutgoingVideoIsScreenShare(boolean isScreenShare) + throws CallException + { + checkCallManagerExists(); + + CallContext callContext = ringrtcGetActiveCallContext(nativeCallManager); + callContext.setOutgoingVideoIsScreenShare(isScreenShare); + + ringrtcSetOutgoingVideoIsScreenShare(nativeCallManager, isScreenShare); + } + /** * * Sets a data mode, allowing the client to limit the media bandwidth used. @@ -1243,7 +1268,10 @@ public class CallManager { /** * - * Creates and returns a GroupCall object. + * Creates and returns a GroupCall object. Defaults call to: + * - videoEnabled == false + * - audioEnabled == false + * - isScreenshare == false * * If there is any error when allocating resources for the object, * null is returned. @@ -1289,7 +1317,11 @@ public class CallManager { /** * - * Creates and returns a GroupCall object for a call link call. + * Creates and returns a GroupCall object for a call link call. Defaults call to: + * - videoEnabled == false + * - audioEnabled == false + * - isScreenshare == false + * * * If there is any error when allocating resources for the object, * null is returned. @@ -1883,6 +1915,14 @@ public class CallManager { */ static class CallContext { + public static final int CAMERA_MAX_WIDTH = 1280; + + public static final int CAMERA_MAX_HEIGHT = 720; + + public static final int CAMERA_MAX_FPS = 30; + + public static final int SCREENSHARE_MAX_FPS = 30; + @NonNull private final String TAG = CallManager.CallContext.class.getSimpleName(); /** CallId */ @NonNull public final CallId callId; @@ -1943,6 +1983,20 @@ public class CallManager { } } + void setOutgoingVideoIsScreenShare(boolean isScreenShare) { + if (this.videoSource != null) { + this.videoSource.setIsScreencast(isScreenShare); + + if (isScreenShare) { + this.videoSource.adaptOutputFormat(VideoSource.AspectRatio.UNDEFINED, null, VideoSource.AspectRatio.UNDEFINED, null, SCREENSHARE_MAX_FPS); + } else { + this.videoSource.adaptOutputFormat(CAMERA_MAX_WIDTH, CAMERA_MAX_HEIGHT, CAMERA_MAX_FPS); + } + } else { + Log.w(TAG, "setOutgoingVideoIsScreenShare(): tried to set isScreenshare but there is no VideoSource"); + } + } + void dispose() { Log.i(TAG, "dispose(): " + callId); @@ -2620,6 +2674,10 @@ public class CallManager { void ringrtcSetVideoEnable(long nativeCallManager, boolean enable) throws CallException; + private native + void ringrtcSetOutgoingVideoIsScreenShare(long nativeCallManager, boolean isScreenShare) + throws CallException; + private native void ringrtcUpdateDataMode(long nativeCallManager, int dataMode) throws CallException; diff --git a/src/android/api/org/signal/ringrtc/GroupCall.java b/src/android/api/org/signal/ringrtc/GroupCall.java index afafefe7..6bdd5ce1 100644 --- a/src/android/api/org/signal/ringrtc/GroupCall.java +++ b/src/android/api/org/signal/ringrtc/GroupCall.java @@ -40,6 +40,14 @@ public final class GroupCall { public static final int INVALID_CLIENT_ID = 0; + public static final int CAMERA_MAX_WIDTH = 640; + + public static final int CAMERA_MAX_HEIGHT = 360; + + public static final int CAMERA_MAX_FPS = 30; + + public static final int SCREENSHARE_MAX_FPS = 30; + @NonNull private static final String TAG = GroupCall.class.getSimpleName(); @NonNull private Kind kind; @@ -101,7 +109,7 @@ public final class GroupCall { this.outgoingAudioTrack.setEnabled(false); } - this.outgoingVideoSource = factory.createVideoSource(false); + this.outgoingVideoSource = factory.createVideoSource(/* isScreencast */false); if (this.outgoingVideoSource == null) { return; } @@ -114,14 +122,17 @@ public final class GroupCall { this.outgoingVideoTrack.setEnabled(false); } - // Define maximum output video format for group calls. - this.outgoingVideoSource.adaptOutputFormat(640, 360, 30); + // Define maximum camera video format for group calls. + this.outgoingVideoSource.adaptOutputFormat(CAMERA_MAX_WIDTH, CAMERA_MAX_HEIGHT, CAMERA_MAX_FPS); this.incomingVideoTracks = new ArrayList<>(); } /** - * Creates a GroupCall object. + * Creates a GroupCall object. Defaults call to: + * - videoEnabled == false + * - audioEnabled == false + * - isScreenshare == false * * Will return null on failure. Should only be accessed via the CallManager.createGroupCall(). */ @@ -436,17 +447,62 @@ public final class GroupCall { * @throws CallException for native code failures * */ - public void setOutgoingVideoMuted(boolean muted) + public void setOutgoingVideoMuted(boolean muted, boolean isScreenShare) throws CallException { Log.i(TAG, "setOutgoingVideoMuted():"); this.localDeviceState.videoMuted = muted; this.outgoingVideoTrack.setEnabled(!this.localDeviceState.videoMuted); + this.setOutgoingVideoIsScreenShare(isScreenShare); ringrtcSetOutgoingVideoMuted(nativeCallManager, this.clientId, muted); } + /** + * + * Indicates whether the outgoing video is a screen share. + * This updates the local device state and sends the status to the SFU. + * + * @param isScreenShare true if the outgoing video is a screen share + * + * @throws CallException for native code failures + * + */ + public void setOutgoingVideoIsScreenShare(boolean isScreenShare) + throws CallException + { + Log.i(TAG, String.format("setOutgoingVideoIsScreenShare(): %b", isScreenShare)); + + this.outgoingVideoSource.setIsScreencast(isScreenShare); + if (isScreenShare) { + this.outgoingVideoSource.adaptOutputFormat(VideoSource.AspectRatio.UNDEFINED, null, VideoSource.AspectRatio.UNDEFINED, null, SCREENSHARE_MAX_FPS); + } else { + this.outgoingVideoSource.adaptOutputFormat(CAMERA_MAX_WIDTH, CAMERA_MAX_HEIGHT, CAMERA_MAX_FPS); + } + + this.localDeviceState.sharingScreen = isScreenShare; + ringrtcSetOutgoingVideoIsScreenShare(nativeCallManager, this.clientId, isScreenShare); + } + + /** + * + * Indicates whether the user is presenting. + * This updates the local device state and sends the status to the SFU. + * + * @param isPresenting true if the outgoing video is a screen share + * + * @throws CallException for native code failures + * + */ + public void setPresenting(boolean isPresenting) + throws CallException + { + Log.i(TAG, "setPresenting():"); + this.localDeviceState.presenting = isPresenting; + ringrtcSetPresenting(nativeCallManager, this.clientId, isPresenting); + } + /** * * Links the camera to the outgoing video track. @@ -990,6 +1046,8 @@ public final class GroupCall { JoinState joinState; boolean audioMuted; boolean videoMuted; + boolean presenting; + boolean sharingScreen; NetworkRoute networkRoute; int audioLevel; @Nullable Long demuxId; @@ -999,6 +1057,8 @@ public final class GroupCall { this.joinState = JoinState.NOT_JOINED; this.audioMuted = true; this.videoMuted = true; + this.presenting = false; + this.sharingScreen = false; this.networkRoute = new NetworkRoute(); this.audioLevel = 0; } @@ -1008,6 +1068,8 @@ public final class GroupCall { this.joinState = localDeviceState.joinState; this.audioMuted = localDeviceState.audioMuted; this.videoMuted = localDeviceState.videoMuted; + this.presenting = localDeviceState.presenting; + this.sharingScreen = localDeviceState.sharingScreen; this.networkRoute = localDeviceState.networkRoute; this.audioLevel = localDeviceState.audioLevel; this.demuxId = localDeviceState.demuxId; @@ -1029,6 +1091,14 @@ public final class GroupCall { return videoMuted; } + public boolean getPresenting() { + return presenting; + } + + public boolean getSharingScreen() { + return sharingScreen; + } + public NetworkRoute getNetworkRoute() { return networkRoute; } @@ -1375,6 +1445,18 @@ public final class GroupCall { boolean muted) throws CallException; + private native + void ringrtcSetPresenting(long nativeCallManager, + long clientId, + boolean isPresenting) + throws CallException; + + private native + void ringrtcSetOutgoingVideoIsScreenShare(long nativeCallManager, + long clientId, + boolean isScreenShare) + throws CallException; + private native void ringrtcRing( long nativeCallManager, long clientId, diff --git a/src/rust/src/android/api/jni_call_manager.rs b/src/rust/src/android/api/jni_call_manager.rs index 1ffebf7d..cb1fb0e4 100644 --- a/src/rust/src/android/api/jni_call_manager.rs +++ b/src/rust/src/android/api/jni_call_manager.rs @@ -561,6 +561,25 @@ pub unsafe extern "C" fn Java_org_signal_ringrtc_CallManager_ringrtcSetVideoEnab } } +#[unsafe(no_mangle)] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_org_signal_ringrtc_CallManager_ringrtcSetOutgoingVideoIsScreenShare( + mut env: JNIEnv, + _object: JObject, + call_manager: jlong, + is_screenshare: jboolean, +) { + match call_manager::set_outgoing_video_is_screenshare( + call_manager as *mut AndroidCallManager, + is_screenshare != 0, + ) { + Ok(v) => v, + Err(e) => { + error::throw_error(&mut env, e); + } + } +} + #[unsafe(no_mangle)] #[allow(non_snake_case)] pub extern "C" fn Java_org_signal_ringrtc_CallManager_ringrtcUpdateDataMode( @@ -1057,6 +1076,48 @@ pub unsafe extern "C" fn Java_org_signal_ringrtc_GroupCall_ringrtcSetOutgoingVid } } +#[unsafe(no_mangle)] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_org_signal_ringrtc_GroupCall_ringrtcSetPresenting( + mut env: JNIEnv, + _object: JObject, + call_manager: jlong, + client_id: jlong, + presenting: jboolean, +) { + match call_manager::set_presenting( + call_manager as *mut AndroidCallManager, + client_id as group_call::ClientId, + presenting != 0, + ) { + Ok(v) => v, + Err(e) => { + error::throw_error(&mut env, e); + } + } +} + +#[unsafe(no_mangle)] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_org_signal_ringrtc_GroupCall_ringrtcSetOutgoingVideoIsScreenShare( + mut env: JNIEnv, + _object: JObject, + call_manager: jlong, + client_id: jlong, + is_screenshare: jboolean, +) { + match call_manager::set_outgoing_group_call_video_is_screenshare( + call_manager as *mut AndroidCallManager, + client_id as group_call::ClientId, + is_screenshare != 0, + ) { + Ok(v) => v, + Err(e) => { + error::throw_error(&mut env, e); + } + } +} + #[unsafe(no_mangle)] #[allow(non_snake_case)] pub unsafe extern "C" fn Java_org_signal_ringrtc_GroupCall_ringrtcRing( diff --git a/src/rust/src/android/call_manager.rs b/src/rust/src/android/call_manager.rs index ee1fbdbe..b659bada 100644 --- a/src/rust/src/android/call_manager.rs +++ b/src/rust/src/android/call_manager.rs @@ -558,6 +558,20 @@ pub fn set_video_enable(call_manager: *mut AndroidCallManager, enable: bool) -> } } +/// CMI request to set whether the outgoing video is a screen share +pub fn set_outgoing_video_is_screenshare( + call_manager: *mut AndroidCallManager, + is_screenshare: bool, +) -> Result<()> { + let call_manager = unsafe { ptr_as_mut(call_manager)? }; + + if let Ok(mut active_connection) = call_manager.active_connection() { + active_connection.send_is_screenshare_update(is_screenshare) + } else { + Ok(()) + } +} + /// Request to update the data mode on the direct connection pub fn update_data_mode(call_manager: *mut AndroidCallManager, data_mode: DataMode) -> Result<()> { let call_manager = unsafe { ptr_as_mut(call_manager)? }; @@ -1051,6 +1065,26 @@ pub fn set_outgoing_video_muted( Ok(()) } +pub fn set_presenting( + call_manager: *mut AndroidCallManager, + client_id: group_call::ClientId, + presenting: bool, +) -> Result<()> { + let call_manager = unsafe { ptr_as_mut(call_manager)? }; + call_manager.set_presenting(client_id, presenting); + Ok(()) +} + +pub fn set_outgoing_group_call_video_is_screenshare( + call_manager: *mut AndroidCallManager, + client_id: group_call::ClientId, + is_screenshare: bool, +) -> Result<()> { + let call_manager = unsafe { ptr_as_mut(call_manager)? }; + call_manager.set_sharing_screen(client_id, is_screenshare); + Ok(()) +} + pub fn group_ring( env: &JNIEnv, call_manager: *mut AndroidCallManager, diff --git a/src/rust/src/core/connection.rs b/src/rust/src/core/connection.rs index 2331a4f8..ca5f6cd0 100644 --- a/src/rust/src/core/connection.rs +++ b/src/rust/src/core/connection.rs @@ -1606,6 +1606,13 @@ where call.connect_incoming_media(incoming_media) } + pub fn send_is_screenshare_update(&mut self, is_screenshare: bool) -> Result<()> { + self.update_sender_status(signaling::SenderStatus { + sharing_screen: Some(is_screenshare), + ..Default::default() + }) + } + /// Send a ConnectionEvent to the internal FSM. fn inject_event(&mut self, event: ConnectionEvent) -> Result<()> { self.fsm_sender diff --git a/src/rust/src/electron.rs b/src/rust/src/electron.rs index 584308f0..ce783389 100644 --- a/src/rust/src/electron.rs +++ b/src/rust/src/electron.rs @@ -1376,10 +1376,7 @@ fn setOutgoingVideoIsScreenShare(mut cx: FunctionContext) -> JsResult { .adapt_output_format(width, height, fps); if let Ok(mut active_connection) = endpoint.call_manager.active_connection() { - active_connection.update_sender_status(signaling::SenderStatus { - sharing_screen: Some(is_screenshare), - ..Default::default() - })?; + active_connection.send_is_screenshare_update(is_screenshare)?; } Ok(()) })