has_video_initially

This commit is contained in:
Casper Zandbergen
2026-01-06 13:28:31 +01:00
parent 9d7db20225
commit ee7aa989bf
9 changed files with 30 additions and 43 deletions

View File

@@ -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);
/**

View File

@@ -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())

View File

@@ -2141,10 +2141,11 @@ impl CommandApi {
account_id: u32,
chat_id: u32,
place_call_info: String,
has_video_initially: bool,
) -> Result<u32> {
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())
}

View File

@@ -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,
})
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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<MsgId> {
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<bool> {
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 {

View File

@@ -52,7 +52,7 @@ async fn setup_call() -> Result<CallSetup> {
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?;

View File

@@ -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