use std::{
collections::HashSet,
sync::{Arc, Mutex},
time::SystemTime,
};
use matrix_sdk_common::executor::spawn;
use ruma::{
OwnedRoomId, SecondsSinceUnixEpoch,
events::{OriginalSyncStateEvent, call::member::CallMemberEventContent},
};
use tracing::warn;
use crate::{Client, Room, client::WeakClient, event_handler::EventHandlerHandle};
pub(crate) struct AutomaticCallStatus {
handle: EventHandlerHandle,
client: WeakClient,
}
type ActiveCallRooms = Arc<Mutex<HashSet<OwnedRoomId>>>;
impl Client {
pub fn enable_automatic_call_status(&self, enabled: bool) {
let mut automatic_call_status = self.inner.automatic_call_status.lock().unwrap();
match (enabled, automatic_call_status.is_some()) {
(true, false) => {
*automatic_call_status = Some(AutomaticCallStatus::new(self));
}
(false, true) => *automatic_call_status = None,
_ => {}
}
}
}
impl AutomaticCallStatus {
fn new(client: &Client) -> Self {
let rooms: ActiveCallRooms = Arc::new(Mutex::new(HashSet::new()));
let handle = client.add_event_handler(
async move |event: OriginalSyncStateEvent<CallMemberEventContent>,
room: Room,
client: Client| {
on_event(&rooms, event, room, client);
},
);
let weak_client = WeakClient::from_client(client);
Self { handle, client: weak_client }
}
}
impl Drop for AutomaticCallStatus {
fn drop(&mut self) {
if let Some(client) = self.client.get() {
client.remove_event_handler(self.handle.clone());
}
}
}
fn on_event(
rooms: &ActiveCallRooms,
event: OriginalSyncStateEvent<CallMemberEventContent>,
room: Room,
client: Client,
) {
let Some(own_user_id) = client.user_id() else { return };
let Some(own_device_id) = client.device_id() else { return };
if event.state_key.user_id() != own_user_id {
return;
}
let is_device_in_room_call = room.is_device_in_active_room_call(own_user_id, own_device_id);
let room_id = room.room_id().to_owned();
let (was_in_call, now_in_call) = {
let mut rooms = rooms.lock().unwrap();
let was_in_call = !rooms.is_empty();
if is_device_in_room_call {
rooms.insert(room_id);
} else {
rooms.remove(&room_id);
}
(was_in_call, !rooms.is_empty())
};
if was_in_call == now_in_call {
return;
}
let active_call_rooms = rooms.clone();
spawn(async move {
let in_call = !active_call_rooms.lock().unwrap().is_empty();
let result = if in_call {
let joined_ts = SecondsSinceUnixEpoch::from_system_time(SystemTime::now());
client.account().set_call(joined_ts).await
} else {
client.account().clear_call().await
};
if let Err(error) = result {
warn!(?error, in_call, "m.call auto-sync request failed");
}
});
}