>,
audio: &mut AudioSystem,
state: &State,
) -> Result<(), Error> {
use Command::*;
let Some(session) = state.server.read().session else {
bail!("no session id")
};
match command {
SendChat { markdown, channels } => {
use markdown::*;
let blocks = tokenize(&markdown);
let html_text = match blocks.as_slice() {
[Block::Paragraph(par)] => match par.as_slice() {
[Span::Text(text)] => text.to_string(),
_ => to_html(&markdown)
.trim()
.strip_prefix("")
.unwrap()
.strip_suffix("
")
.unwrap()
.to_string(),
},
_ => to_html(&markdown).trim().to_string(),
};
{
let mut server = state.server.write_unchecked();
let Some(me) = server.session else {
bail!("not signed in with a session id")
};
server.chat.push(Chat {
raw: markdown,
dangerous_html: html_text.clone(),
sender: Some(me),
})
}
let mut u = msgs::TextMessage::new();
u.set_message(html_text.to_string());
u.set_channel_id(channels);
let _ = send_chan.unbounded_send(u.into());
}
SendFile {
ref bytes,
name,
mime,
channels,
} => {
use base64::{display::Base64Display, prelude::BASE64_STANDARD};
let html = match mime {
Some(mime) if mime.type_() == "image" => format!(
"
",
mime,
Base64Display::new(bytes, &BASE64_STANDARD)
),
Some(mime) => format!(
"{name}",
mime,
Base64Display::new(bytes, &BASE64_STANDARD)
),
None => format!(
"{name}",
Base64Display::new(bytes, &BASE64_STANDARD)
),
};
{
let mut server = state.server.write_unchecked();
let Some(me) = server.session else {
bail!("not signed in with a session id")
};
server.chat.push(Chat {
raw: "".to_string(),
dangerous_html: html.clone(),
sender: Some(me),
})
}
let mut u = msgs::TextMessage::new();
u.set_message(html);
u.set_channel_id(channels);
let _ = send_chan.unbounded_send(u.into());
}
SetMute { mute } => {
let mut u = msgs::UserState::new();
u.set_session(session);
u.set_self_mute(mute);
let _ = send_chan.unbounded_send(u.into());
}
SetDeaf { deaf } => {
let mut u = msgs::UserState::new();
u.set_session(session);
u.set_self_deaf(deaf);
let _ = send_chan.unbounded_send(u.into());
}
EnterChannel { channel, user } => {
let mut u = msgs::UserState::new();
u.set_session(user);
u.set_channel_id(channel);
let _ = send_chan.unbounded_send(u.into());
}
Connect { .. } | Disconnect => (),
UpdateAudioSettings(AudioSettings { denoise }) => {
audio.set_processor(AudioProcessor::new(denoise));
}
}
Ok(())
}
fn accept_packet(
msg: ControlPacket,
audio_context: &mut AudioSystem,
player_map: &mut HashMap,
state: &State,
) -> Result<(), Error> {
match msg {
ControlPacket::UDPTunnel(u) => {
match *u.clone() {
mumble_protocol::voice::VoicePacket::Audio {
_dst,
target,
session_id,
seq_num,
payload,
position_info,
} => {
// Get or create audio decoder for this user
let audio_player = match player_map.entry(session_id) {
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
Entry::Vacant(vacant_entry) => {
vacant_entry.insert(audio_context.create_player()?)
}
};
// This will over time (as users join and leave) leak
// AudioDecoders, MediaStreamTrackGenerators, MediaStreams, and MediaStreamAudioSourceNodes.
// A better way to handle this would be to delete and create all the audio
// infra on channel join and update it as new users join the channel, dropping
// any audio packets that come in the meantime.
if let VoicePacketPayload::Opus(audio_payload, end_bit) = payload {
audio_player.play_opus(audio_payload.as_ref());
//console::log_1(&"Oueued audio chunk for decoding".into());
}
}
_ => {
unreachable!("TCP tunnels UDP packets should not contain pings");
// I think?
}
}
}
ControlPacket::ChannelState(u) => {
let mut server = state.server.write_unchecked();
server.channels_state.update_from_channel_state(&u);
}
ControlPacket::ChannelRemove(u) => {
let mut server = state.server.write_unchecked();
server.channels_state.update_from_channel_remove(&u);
}
ControlPacket::UserState(u) => {
let mut server = state.server.write_unchecked();
let server = &mut *server;
let id = u.get_session();
let state_entry = server.users.entry(id);
let new = matches!(state_entry, std::collections::hash_map::Entry::Vacant(_));
let state = state_entry.or_default();
// the server might now send a channel_id if the user is in channel=0
if u.has_channel_id() || new {
if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) {
parent.users.remove(&id);
}
let channel_id = u.get_channel_id();
server
.channels_state
.channels
.entry(channel_id)
.or_default()
.users
.insert(id);
state.channel = channel_id;
}
if u.has_name() {
state.name = u.get_name().to_string();
}
if u.has_mute() {
state.mute = u.get_mute();
}
if u.has_deaf() {
state.deaf = u.get_deaf();
}
if u.has_suppress() {
state.suppress = u.get_suppress();
}
if u.has_self_mute() {
state.self_mute = u.get_self_mute();
}
if u.has_self_deaf() {
state.self_deaf = u.get_self_deaf();
}
}
ControlPacket::UserRemove(u) => {
let mut server = state.server.write_unchecked();
let id = u.get_session();
if let Some(state) = server.users.remove(&id) {
if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) {
parent.users.remove(&id);
}
}
}
ControlPacket::TextMessage(u) => {
let mut server = state.server.write_unchecked();
if u.has_message() {
let text = u.get_message().to_string();
server.chat.push(Chat {
sender: if u.has_actor() {
Some(u.get_actor())
} else {
None
},
dangerous_html: process_message_html(&text),
raw: text,
});
}
}
ControlPacket::ServerSync(u) => {
*state.status.write_unchecked() = ConnectionState::Connected;
let mut server = state.server.write_unchecked();
if u.has_welcome_text() {
let text = u.get_welcome_text().to_string();
server.chat.push(Chat {
sender: None,
dangerous_html: process_message_html(&text),
raw: text,
});
}
if u.has_session() {
server.session = Some(u.get_session());
}
}
_ => {}
}
Ok(())
}