Skip to content

Commit 129e970

Browse files
committed
api: add has_video attribute to incoming call events
This allows UI to show if incoming call is a video or audio call and disable camera by default for audio calls.
1 parent 66271db commit 129e970

File tree

9 files changed

+116
-4
lines changed

9 files changed

+116
-4
lines changed

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ rusqlite = { workspace = true, features = ["sqlcipher"] }
8989
rustls-pki-types = "1.12.0"
9090
rustls = { version = "0.23.22", default-features = false }
9191
sanitize-filename = { workspace = true }
92+
sdp = "0.8.0"
9293
serde_json = { workspace = true }
9394
serde_urlencoded = "0.7.1"
9495
serde = { workspace = true, features = ["derive"] }

deltachat-ffi/deltachat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6732,6 +6732,7 @@ void dc_event_unref(dc_event_t* event);
67326732
*
67336733
* @param data1 (int) msg_id ID of the message referring to the call.
67346734
* @param data2 (char*) place_call_info, text passed to dc_place_outgoing_call()
6735+
* @param data2 (int) 1 if incoming call is a video call, 0 otherwise
67356736
*/
67366737
#define DC_EVENT_INCOMING_CALL 2550
67376738

deltachat-ffi/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,6 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
679679
| EventType::ChatModified(_)
680680
| EventType::ChatDeleted { .. }
681681
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
682-
| EventType::IncomingCall { .. }
683682
| EventType::IncomingCallAccepted { .. }
684683
| EventType::OutgoingCallAccepted { .. }
685684
| EventType::CallEnded { .. }
@@ -701,6 +700,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
701700
..
702701
} => status_update_serial.to_u32() as libc::c_int,
703702
EventType::WebxdcRealtimeData { data, .. } => data.len() as libc::c_int,
703+
EventType::IncomingCall { has_video, .. } => *has_video as libc::c_int,
704+
704705
#[allow(unreachable_patterns)]
705706
#[cfg(test)]
706707
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),

deltachat-jsonrpc/src/api/types/events.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ pub enum EventType {
427427
msg_id: u32,
428428
/// User-defined info as passed to place_outgoing_call()
429429
place_call_info: String,
430+
/// True if incoming call is a video call.
431+
has_video: bool,
430432
},
431433

432434
/// Incoming call accepted.
@@ -604,9 +606,11 @@ impl From<CoreEventType> for EventType {
604606
CoreEventType::IncomingCall {
605607
msg_id,
606608
place_call_info,
609+
has_video,
607610
} => IncomingCall {
608611
msg_id: msg_id.to_u32(),
609612
place_call_info,
613+
has_video,
610614
},
611615
CoreEventType::IncomingCallAccepted { msg_id } => IncomingCallAccepted {
612616
msg_id: msg_id.to_u32(),

deltachat-rpc-client/tests/test_calls.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def test_calls(acfactory) -> None:
1313

1414
incoming_call_event = bob.wait_for_event(EventType.INCOMING_CALL)
1515
assert incoming_call_event.place_call_info == place_call_info
16+
assert not incoming_call_event.has_video # Cannot be parsed as SDP, so false by default
1617
incoming_call_message = Message(bob, incoming_call_event.msg_id)
1718

1819
incoming_call_message.accept_incoming_call(accept_call_info)
@@ -23,3 +24,45 @@ def test_calls(acfactory) -> None:
2324

2425
end_call_event = bob.wait_for_event(EventType.CALL_ENDED)
2526
assert end_call_event.msg_id == outgoing_call_message.id
27+
28+
29+
def test_video_call(acfactory) -> None:
30+
# Example from <https://datatracker.ietf.org/doc/rfc9143/>
31+
# with `s= ` replaced with `s=-`.
32+
#
33+
# `s=` cannot be empty according to RFC 3264,
34+
# so it is more clear as `s=-`.
35+
place_call_info = """v=0\r
36+
o=alice 2890844526 2890844526 IN IP6 2001:db8::3\r
37+
s=-\r
38+
c=IN IP6 2001:db8::3\r
39+
t=0 0\r
40+
a=group:BUNDLE foo bar\r
41+
\r
42+
m=audio 10000 RTP/AVP 0 8 97\r
43+
b=AS:200\r
44+
a=mid:foo\r
45+
a=rtcp-mux\r
46+
a=rtpmap:0 PCMU/8000\r
47+
a=rtpmap:8 PCMA/8000\r
48+
a=rtpmap:97 iLBC/8000\r
49+
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r
50+
\r
51+
m=video 10002 RTP/AVP 31 32\r
52+
b=AS:1000\r
53+
a=mid:bar\r
54+
a=rtcp-mux\r
55+
a=rtpmap:31 H261/90000\r
56+
a=rtpmap:32 MPV/90000\r
57+
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r
58+
"""
59+
60+
alice, bob = acfactory.get_online_accounts(2)
61+
62+
alice_contact_bob = alice.create_contact(bob, "Bob")
63+
alice_chat_bob = alice_contact_bob.create_chat()
64+
alice_chat_bob.place_outgoing_call(place_call_info)
65+
66+
incoming_call_event = bob.wait_for_event(EventType.INCOMING_CALL)
67+
assert incoming_call_event.place_call_info == place_call_info
68+
assert incoming_call_event.has_video

src/calls.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ use crate::contact::ContactId;
88
use crate::context::Context;
99
use crate::events::EventType;
1010
use crate::headerdef::HeaderDef;
11-
use crate::log::info;
11+
use crate::log::{info, warn};
1212
use crate::message::{self, Message, MsgId, Viewtype};
1313
use crate::mimeparser::{MimeMessage, SystemMessage};
1414
use crate::param::Param;
1515
use crate::tools::time;
16-
use anyhow::{Result, ensure};
16+
use anyhow::{Context as _, Result, ensure};
17+
use sdp::SessionDescription;
18+
use std::io::Cursor;
1719
use std::time::Duration;
1820
use tokio::task;
1921
use tokio::time::sleep;
@@ -259,16 +261,25 @@ impl Context {
259261
) -> Result<()> {
260262
if mime_message.is_call() {
261263
let call = self.load_call_by_id(call_id).await?;
264+
262265
if call.is_incoming() {
263266
if call.is_stale() {
264267
call.update_text(self, "Missed call").await?;
265268
self.emit_incoming_msg(call.msg.chat_id, call_id); // notify missed call
266269
} else {
267270
call.update_text(self, "Incoming call").await?;
268271
self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified
272+
let has_video = match sdp_has_video(&call.place_call_info) {
273+
Ok(has_video) => has_video,
274+
Err(err) => {
275+
warn!(self, "Failed to determine if SDP offer has video: {err:#}.");
276+
false
277+
}
278+
};
269279
self.emit_event(EventType::IncomingCall {
270280
msg_id: call.msg.id,
271281
place_call_info: call.place_call_info.to_string(),
282+
has_video,
272283
});
273284
let wait = call.remaining_ring_seconds();
274285
task::spawn(Context::emit_end_call_if_unaccepted(
@@ -369,5 +380,18 @@ impl Context {
369380
}
370381
}
371382

383+
/// Returns true if SDP offer has a video.
384+
fn sdp_has_video(sdp: &str) -> Result<bool> {
385+
let mut cursor = Cursor::new(sdp);
386+
let session_description =
387+
SessionDescription::unmarshal(&mut cursor).context("Failed to parse SDP")?;
388+
for media_description in &session_description.media_descriptions {
389+
if media_description.media_name.media == "video" {
390+
return Ok(true);
391+
}
392+
}
393+
Ok(false)
394+
}
395+
372396
#[cfg(test)]
373397
mod calls_tests;

src/calls/calls_tests.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@ async fn assert_text(t: &TestContext, call_id: MsgId, text: &str) -> Result<()>
1919
}
2020

2121
// Offer and answer examples from <https://www.rfc-editor.org/rfc/rfc3264>
22-
const PLACE_INFO: &str = "v=0\r\no=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\ns=\r\nc=IN IP4 host.anywhere.com\r\nt=0 0\r\nm=audio 62986 RTP/AVP 0 4 18\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:4 G723/8000\r\na=rtpmap:18 G729/8000\r\na=inactive\r\n";
22+
const PLACE_INFO: &str = "v=0\r\no=alice 2890844526 2890844526 IN IP4 host.anywhere.com\r\ns=-\r\nc=IN IP4 host.anywhere.com\r\nt=0 0\r\nm=audio 62986 RTP/AVP 0 4 18\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:4 G723/8000\r\na=rtpmap:18 G729/8000\r\na=inactive\r\n";
2323
const ACCEPT_INFO: &str = "v=0\r\no=bob 2890844730 2890844731 IN IP4 host.example.com\r\ns=\r\nc=IN IP4 host.example.com\r\nt=0 0\r\nm=audio 54344 RTP/AVP 0 4\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:4 G723/8000\r\na=inactive\r\n";
2424

25+
/// Example from <https://datatracker.ietf.org/doc/rfc9143/>
26+
/// with `s= ` replaced with `s=-`.
27+
///
28+
/// `s=` cannot be empty according to RFC 3264,
29+
/// so it is more clear as `s=-`.
30+
const PLACE_INFO_VIDEO: &str = "v=0\r\no=alice 2890844526 2890844526 IN IP6 2001:db8::3\r\ns=-\r\nc=IN IP6 2001:db8::3\r\nt=0 0\r\na=group:BUNDLE foo bar\r\n\r\nm=audio 10000 RTP/AVP 0 8 97\r\nb=AS:200\r\na=mid:foo\r\na=rtcp-mux\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:97 iLBC/8000\r\na=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\r\nm=video 10002 RTP/AVP 31 32\r\nb=AS:1000\r\na=mid:bar\r\na=rtcp-mux\r\na=rtpmap:31 H261/90000\r\na=rtpmap:32 MPV/90000\r\na=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r\n";
31+
2532
async fn setup_call() -> Result<CallSetup> {
2633
let mut tcm = TestContextManager::new();
2734
let alice = tcm.alice().await;
@@ -417,3 +424,10 @@ async fn test_update_call_text() -> Result<()> {
417424

418425
Ok(())
419426
}
427+
428+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
429+
async fn test_sdp_has_video() {
430+
assert!(sdp_has_video("foobar").is_err());
431+
assert_eq!(sdp_has_video(PLACE_INFO).unwrap(), false);
432+
assert_eq!(sdp_has_video(PLACE_INFO_VIDEO).unwrap(), true);
433+
}

src/events/payload.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@ pub enum EventType {
386386
msg_id: MsgId,
387387
/// User-defined info as passed to place_outgoing_call()
388388
place_call_info: String,
389+
/// True if incoming call is a video call.
390+
has_video: bool,
389391
},
390392

391393
/// Incoming call accepted.

0 commit comments

Comments
 (0)