Voice requests

This commit is contained in:
2026-01-06 19:46:59 +01:00
parent 81c59a578d
commit 36fc8da82b
6 changed files with 156 additions and 3 deletions
+26
View File
@@ -43,6 +43,9 @@ impl Server {
ClientMessage::Typing { channel_id } => { ClientMessage::Typing { channel_id } => {
indicator::start_typing(self, client, channel_id)? indicator::start_typing(self, client, channel_id)?
} }
ClientMessage::JoinVoice { channel_id } => voice::join(self, client, channel_id)?,
ClientMessage::LeaveVoice { channel_id } => voice::leave(self, client, channel_id)?,
}, },
WsMessage::Binary(data) => { WsMessage::Binary(data) => {
@@ -70,4 +73,27 @@ impl Server {
}); });
} }
} }
pub fn broadcast_bin_to(
self: &Arc<Self>,
targets: &[&String],
bytes: Vec<u8>,
) -> crate::Result<()> {
for c in self.clients.lock().unwrap().iter() {
if !targets.contains(&&c.get_uuid()?) {
continue;
}
let c = c.clone();
let server = self.clone();
let bytes = bytes.clone();
std::thread::spawn(move || {
server
.wrap_err(&c, c.send_bin(&bytes))
.expect("Failed to broadcast");
});
}
Ok(())
}
} }
+45 -2
View File
@@ -3,13 +3,56 @@ use std::sync::Arc;
crate::logger!(LOGGER "Voice chat"); crate::logger!(LOGGER "Voice chat");
pub fn voice(server: &Arc<Server>, client: &Client, data: &[u8]) -> crate::Result<()> { pub fn join(server: &Arc<Server>, client: &Client, channel_id: &str) -> crate::Result<()> {
let prefix = (10u16).to_le_bytes(); let user_id = client.get_uuid()?;
let voice_id = server
.voice
.lock()
.unwrap()
.set(user_id.clone(), channel_id.to_string());
server.broadcast(crate::types::message::ServerMessage::VoiceJoin {
user_id,
channel_id: channel_id.to_string(),
voice_id: voice_id,
});
Ok(())
}
pub fn leave(server: &Arc<Server>, client: &Client, channel_id: &str) -> crate::Result<()> {
let user_id = client.get_uuid()?;
let Some(voice_id) = server.voice.lock().unwrap().remove(channel_id, &user_id) else {
return Ok(());
};
server.broadcast(crate::types::message::ServerMessage::VoiceLeave {
user_id,
channel_id: channel_id.to_string(),
voice_id,
});
Ok(())
}
pub fn voice(server: &Arc<Server>, client: &Client, data: &[u8]) -> crate::Result<()> {
let v = server.voice.lock().unwrap();
let Some((channel_id, voice_id)) = v.find_user(&client.get_uuid()?) else {
return Ok(());
};
let targets = v.get(&channel_id);
let prefix = voice_id.to_le_bytes();
let mut payload = Vec::with_capacity(prefix.len() + data.len()); let mut payload = Vec::with_capacity(prefix.len() + data.len());
payload.extend_from_slice(&prefix); payload.extend_from_slice(&prefix);
payload.extend_from_slice(data); payload.extend_from_slice(data);
client.send_bin(&payload)?; client.send_bin(&payload)?;
server.broadcast_bin_to(&targets, payload)?;
Ok(()) Ok(())
} }
+3 -1
View File
@@ -25,7 +25,7 @@ use crate::{
cli, logger, cli, logger,
plugin::{Plugin, loader::PluginLoader, types::LoaderMessage}, plugin::{Plugin, loader::PluginLoader, types::LoaderMessage},
types::{self, message::WsMessage}, types::{self, message::WsMessage},
utils::{self, auth, client::Client}, utils::{self, auth, client::Client, voice::Voice},
}; };
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
@@ -45,6 +45,7 @@ pub struct Server {
pub db: utils::database::Database, pub db: utils::database::Database,
pub shutting_down: AtomicBool, pub shutting_down: AtomicBool,
pub indicators: Mutex<Vec<crate::requests::indicator::IndicatorContext>>, pub indicators: Mutex<Vec<crate::requests::indicator::IndicatorContext>>,
pub voice: Mutex<crate::utils::voice::Voice>,
} }
impl Default for ServerConfig { impl Default for ServerConfig {
@@ -81,6 +82,7 @@ impl Server {
plugins: Mutex::new(Vec::new()), plugins: Mutex::new(Vec::new()),
shutting_down: AtomicBool::new(false), shutting_down: AtomicBool::new(false),
indicators: Mutex::new(Vec::new()), indicators: Mutex::new(Vec::new()),
voice: Mutex::new(Voice::new()),
}) })
} }
+20
View File
@@ -92,6 +92,14 @@ pub mod message {
Typing { Typing {
channel_id: String, channel_id: String,
}, },
JoinVoice {
channel_id: String,
},
LeaveVoice {
channel_id: String,
},
} }
/// Messages sent *from the server* to the client /// Messages sent *from the server* to the client
@@ -136,6 +144,18 @@ pub mod message {
}, },
Chunk(Vec<Message>), Chunk(Vec<Message>),
VoiceJoin {
user_id: String,
channel_id: String,
voice_id: u16,
},
VoiceLeave {
user_id: String,
channel_id: String,
voice_id: u16,
},
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
+1
View File
@@ -3,3 +3,4 @@ pub mod client;
pub mod database; pub mod database;
pub mod logger; pub mod logger;
pub mod vfs; pub mod vfs;
pub mod voice;
+61
View File
@@ -0,0 +1,61 @@
use std::collections::HashMap;
pub struct Voice {
// channel_id -> (user_id -> voice_id)
connections: HashMap<String, HashMap<String, u16>>,
}
impl Voice {
pub fn new() -> Self {
Self {
connections: HashMap::new(),
}
}
/// Insert or update a user's voice_id in a channel
pub fn set(&mut self, user_id: String, channel_id: String) -> u16 {
let voice_id = rand::random::<u16>();
self.connections
.entry(channel_id)
.or_insert_with(HashMap::new)
.insert(user_id, voice_id);
voice_id
}
/// Remove a user from a channel
/// Returns their voice_id if they existed
pub fn remove(&mut self, channel_id: &str, user_id: &str) -> Option<u16> {
let channel = self.connections.get_mut(channel_id)?;
let voice_id = channel.remove(user_id)?;
// Clean up empty channels
if channel.is_empty() {
self.connections.remove(channel_id);
}
Some(voice_id)
}
/// Get all users + voice_ids in a channel
pub fn get(&self, channel_id: &str) -> Vec<&String> {
self.connections
.get(channel_id)
.map(|users| users.keys().collect())
.unwrap_or_default()
}
/// Check if a user exists in a channel
pub fn check(&self, channel_id: &str, user_id: &str) -> Option<u16> {
self.connections
.get(channel_id)
.and_then(|users| users.get(user_id).copied())
}
pub fn find_user(&self, user_id: &str) -> Option<(&String, u16)> {
self.connections
.iter()
.find_map(|(channel_id, users)| users.get(user_id).map(|v| (channel_id, *v)))
}
}