diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 2b297d6e6..61eeeb3fc 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1235,9 +1235,11 @@ uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t c * This needs to be a one-to-one chat. * @param place_call_info any data that other devices receive * in #DC_EVENT_INCOMING_CALL. + * @param has_video_initially Whether the call has video. + * This allows the recipient's client to adjust UX. * @return ID of the system message announcing the call. */ -uint32_t dc_place_outgoing_call (dc_context_t* context, uint32_t chat_id, const char* place_call_info); +uint32_t dc_place_outgoing_call (dc_context_t* context, uint32_t chat_id, const char* place_call_info, int has_video_initially); /** diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 883d805a9..5638c4a16 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1181,6 +1181,7 @@ pub unsafe extern "C" fn dc_place_outgoing_call( context: *mut dc_context_t, chat_id: u32, place_call_info: *const libc::c_char, + has_video_initially: bool, ) -> u32 { if context.is_null() || chat_id == 0 { eprintln!("ignoring careless call to dc_place_outgoing_call()"); @@ -1190,7 +1191,7 @@ pub unsafe extern "C" fn dc_place_outgoing_call( let chat_id = ChatId::new(chat_id); let place_call_info = to_string_lossy(place_call_info); - block_on(ctx.place_outgoing_call(chat_id, place_call_info)) + block_on(ctx.place_outgoing_call(chat_id, place_call_info, has_video_initially)) .context("Failed to place call") .log_err(ctx) .map(|msg_id| msg_id.to_u32()) diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 4c97a3103..ffaec97fa 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -2141,10 +2141,11 @@ impl CommandApi { account_id: u32, chat_id: u32, place_call_info: String, + has_video_initially: bool, ) -> Result { let ctx = self.get_context(account_id).await?; let msg_id = ctx - .place_outgoing_call(ChatId::new(chat_id), place_call_info) + .place_outgoing_call(ChatId::new(chat_id), place_call_info, has_video_initially) .await?; Ok(msg_id.to_u32()) } diff --git a/deltachat-jsonrpc/src/api/types/calls.rs b/deltachat-jsonrpc/src/api/types/calls.rs index e779f1c89..563005cb5 100644 --- a/deltachat-jsonrpc/src/api/types/calls.rs +++ b/deltachat-jsonrpc/src/api/types/calls.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; -use deltachat::calls::{call_state, sdp_has_video, CallState}; +use deltachat::calls::{call_state, CallState}; use deltachat::context::Context; use deltachat::message::MsgId; use serde::Serialize; @@ -15,8 +15,8 @@ pub struct JsonrpcCallInfo { /// even if incoming call event was missed. pub sdp_offer: String, - /// True if SDP offer has a video. - pub has_video: bool, + /// True if the call is started as a video call. + pub has_video_initially: bool, /// Call state. /// @@ -30,12 +30,12 @@ impl JsonrpcCallInfo { format!("Attempting to get call state of non-call message {msg_id}") })?; let sdp_offer = call_info.place_call_info.clone(); - let has_video = sdp_has_video(&sdp_offer).unwrap_or_default(); + let has_video_initially = call_info.has_video_initially(); let state = JsonrpcCallState::from_msg_id(context, msg_id).await?; Ok(JsonrpcCallInfo { sdp_offer, - has_video, + has_video_initially, state, }) } diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py index eef858b92..556af9c05 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py @@ -303,7 +303,7 @@ class Chat: f.flush() self._rpc.send_msg(self.account.id, self.id, {"viewtype": ViewType.VCARD, "file": f.name}) - def place_outgoing_call(self, place_call_info: str) -> Message: + def place_outgoing_call(self, place_call_info: str, has_video_initially: bool) -> Message: """Starts an outgoing call.""" - msg_id = self._rpc.place_outgoing_call(self.account.id, self.id, place_call_info) + msg_id = self._rpc.place_outgoing_call(self.account.id, self.id, place_call_info, has_video_initially) return Message(self.account, msg_id) diff --git a/deltachat-rpc-client/tests/test_calls.py b/deltachat-rpc-client/tests/test_calls.py index cd10c4bee..a7a629eb4 100644 --- a/deltachat-rpc-client/tests/test_calls.py +++ b/deltachat-rpc-client/tests/test_calls.py @@ -10,7 +10,7 @@ def test_calls(acfactory) -> None: alice_contact_bob = alice.create_contact(bob, "Bob") alice_chat_bob = alice_contact_bob.create_chat() bob.create_chat(alice) # Accept the chat so incoming call causes a notification. - outgoing_call_message = alice_chat_bob.place_outgoing_call(place_call_info) + outgoing_call_message = alice_chat_bob.place_outgoing_call(place_call_info, True) assert outgoing_call_message.get_call_info().state.kind == "Alerting" incoming_call_event = bob.wait_for_event(EventType.INCOMING_CALL) @@ -71,7 +71,7 @@ a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r bob.create_chat(alice) # Accept the chat so incoming call causes a notification. alice_contact_bob = alice.create_contact(bob, "Bob") alice_chat_bob = alice_contact_bob.create_chat() - alice_chat_bob.place_outgoing_call(place_call_info) + alice_chat_bob.place_outgoing_call(place_call_info, True) incoming_call_event = bob.wait_for_event(EventType.INCOMING_CALL) assert incoming_call_event.place_call_info == place_call_info @@ -92,7 +92,7 @@ def test_no_contact_request_call(acfactory) -> None: alice, bob = acfactory.get_online_accounts(2) alice_chat_bob = alice.create_chat(bob) - alice_chat_bob.place_outgoing_call("offer") + alice_chat_bob.place_outgoing_call("offer", True) alice_chat_bob.send_text("Hello!") # Notification for "Hello!" message should arrive diff --git a/src/calls.rs b/src/calls.rs index 38f196a94..3639e90eb 100644 --- a/src/calls.rs +++ b/src/calls.rs @@ -123,6 +123,11 @@ impl CallInfo { self.msg.param.exists(CALL_ACCEPTED_TIMESTAMP) } + /// Returns true if the call is started as a video call. + pub fn has_video_initially(&self) -> bool { + self.msg.param.get_bool(Param::CallHasVideoInitially).unwrap_or(false) + } + /// Returns true if the call is missed /// because the caller canceled it /// explicitly before ringing stopped. @@ -182,6 +187,7 @@ impl Context { &self, chat_id: ChatId, place_call_info: String, + has_video_initially: bool, ) -> Result { let chat = Chat::load_from_db(self, chat_id).await?; ensure!( @@ -196,6 +202,7 @@ impl Context { ..Default::default() }; call.param.set(Param::WebrtcRoom, &place_call_info); + call.param.set_int(Param::CallHasVideoInitially, has_video_initially as i32); call.id = send_msg(self, chat_id, &mut call).await?; let wait = RINGING_SECONDS; @@ -341,13 +348,6 @@ impl Context { } else { call.update_text(self, "Incoming call").await?; self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified - let has_video = match sdp_has_video(&call.place_call_info) { - Ok(has_video) => has_video, - Err(err) => { - warn!(self, "Failed to determine if SDP offer has video: {err:#}."); - false - } - }; if let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(self, from_id).await? { @@ -357,7 +357,7 @@ impl Context { msg_id: call.msg.id, chat_id: call.msg.chat_id, place_call_info: call.place_call_info.to_string(), - has_video, + has_video: call.has_video_initially(), }); } Blocked::Yes | Blocked::Request => { @@ -496,19 +496,6 @@ impl Context { } } -/// Returns true if SDP offer has a video. -pub fn sdp_has_video(sdp: &str) -> Result { - let mut cursor = Cursor::new(sdp); - let session_description = - SessionDescription::unmarshal(&mut cursor).context("Failed to parse SDP")?; - for media_description in &session_description.media_descriptions { - if media_description.media_name.media == "video" { - return Ok(true); - } - } - Ok(false) -} - /// State of the call for display in the message bubble. #[derive(Debug, PartialEq, Eq)] pub enum CallState { diff --git a/src/calls/calls_tests.rs b/src/calls/calls_tests.rs index 3f983d843..b333a6044 100644 --- a/src/calls/calls_tests.rs +++ b/src/calls/calls_tests.rs @@ -52,7 +52,7 @@ async fn setup_call() -> Result { bob2.create_chat(&alice).await; let test_msg_id = alice - .place_outgoing_call(alice_chat.id, PLACE_INFO.to_string()) + .place_outgoing_call(alice_chat.id, PLACE_INFO.to_string(), true) .await?; let sent1 = alice.pop_sent_msg().await; assert_eq!(sent1.sender_msg_id, test_msg_id); @@ -525,13 +525,6 @@ async fn test_update_call_text() -> Result<()> { Ok(()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_sdp_has_video() { - assert!(sdp_has_video("foobar").is_err()); - assert_eq!(sdp_has_video(PLACE_INFO).unwrap(), false); - assert_eq!(sdp_has_video(PLACE_INFO_VIDEO).unwrap(), true); -} - /// Tests that calls are forwarded as text messages. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_forward_call() -> Result<()> { @@ -542,7 +535,7 @@ async fn test_forward_call() -> Result<()> { let alice_bob_chat = alice.create_chat(bob).await; let alice_msg_id = alice - .place_outgoing_call(alice_bob_chat.id, PLACE_INFO.to_string()) + .place_outgoing_call(alice_bob_chat.id, PLACE_INFO.to_string(), true) .await .context("Failed to place a call")?; let alice_call = Message::load_from_db(alice, alice_msg_id).await?; diff --git a/src/param.rs b/src/param.rs index 0640b551e..e1b37a224 100644 --- a/src/param.rs +++ b/src/param.rs @@ -148,6 +148,9 @@ pub enum Param { /// For Messages WebrtcAccepted = b'7', + /// For Messages + CallHasVideoInitially = b'8', + /// For Messages: space-separated list of messaged IDs of forwarded copies. /// /// This is used when a [crate::message::Message] is in the