diff --git a/src/vc/mod.rs b/src/vc/mod.rs index c6e91ce..6f809ba 100644 --- a/src/vc/mod.rs +++ b/src/vc/mod.rs @@ -1,20 +1,41 @@ -use std::{sync::Arc, collections::HashMap}; +use std::{collections::HashMap, sync::Arc}; -use serenity::{async_trait, - model::{prelude::{ChannelId, Guild}, user::User}, - prelude::{Context, Mutex, RwLock, TypeMapKey}, Client}; -use songbird::{EventHandler, Event, EventContext, - model::{payload::{Speaking, ClientDisconnect}, id::UserId}, ffmpeg, create_player, Call, CoreEvent, events::context_data::ConnectData}; +use serenity::{ + async_trait, + model::prelude::{ChannelId, Guild, GuildId}, + prelude::{Context, Mutex, RwLock, TypeMapKey}, Client, +}; -struct Receiver { - call : Arc>, - users : Arc>>>, +use songbird::{ + create_player, + events::context_data::{ConnectData, DisconnectData}, + ffmpeg, + model::{ + id::UserId, + payload::{ClientDisconnect, Speaking}, + }, + Call, CoreEvent, Event, EventContext, EventHandler, +}; + +#[derive(Eq, Hash)] +struct CallLocation { + guild: GuildId, + channel: ChannelId } -impl Receiver { +impl PartialEq for CallLocation { + fn eq(&self, other: &Self) -> bool { + self.guild == other.guild && self.channel == other.channel + } +} + +struct VoiceData { + call: Arc>, + users: Arc>>>, +} + +impl VoiceData { pub fn new(call: Arc>) -> Self { - // You can manage state here, such as a buffer of audio packet bytes so - // you can later store them in intervals. Self { call, users: Arc::new(RwLock::new(HashMap::default())) @@ -22,106 +43,132 @@ impl Receiver { } } -struct VCUser; +struct VCData { + loc: Arc, + data: Arc> +} -impl TypeMapKey for VCUser { - type Value = Arc>>; +impl TypeMapKey for VCData { + type Value = Arc>>; +} + +impl VCData { + pub fn new(loc: CallLocation, data: VoiceData) -> Self { + // You can manage state here, such as a buffer of audio packet bytes so + // you can later store them in intervals. + VCData { + loc: Arc::new(loc), + data: Arc::new(RwLock::new(data)) + } + } + pub fn clone(&self) -> Self { + VCData { + loc: self.loc.clone(), + data: self.data.clone() + } + } } pub async fn init(client: &Client) { let mut data = client.data.write().await; - data.insert::(Arc::new(RwLock::new(HashMap::default()))); + data.insert::(Arc::new(RwLock::new(HashMap::default()))) } #[async_trait] -impl EventHandler for Receiver { +impl EventHandler for VCData { #[allow(unused_variables)] async fn act(&self, ctx: &EventContext<'_>) -> Option { - use EventContext as Ctx; - match ctx { - Ctx::SpeakingStateUpdate( - Speaking {speaking, ssrc, user_id, ..} - ) => { - // Discord voice calls use RTP, where every sender uses a randomly allocated - // *Synchronisation Source* (SSRC) to allow receivers to tell which audio - // stream a received packet belongs to. As this number is not derived from - // the sender's user_id, only Discord Voice Gateway messages like this one - // inform us about which random SSRC a user has been allocated. Future voice - // packets will contain *only* the SSRC. - // - // You can implement logic here so that you can differentiate users' - // SSRCs and map the SSRC to the User ID and maintain this state. - // Using this map, you can map the `ssrc` in `voice_packet` - // to the user ID and handle their audio packets separately. - { - let mut users = self.users.write().await; - users.insert(ssrc.clone(), user_id.clone()); - } - println!( - "\n\n\nSpeaking state update: user {:?} has SSRC {:?}, using {:?}\n\n\n", - user_id, - ssrc, - speaking, - ); - }, - Ctx::SpeakingUpdate(data) => { - // You can implement logic here which reacts to a user starting - // or stopping speaking, and to map their SSRC to User ID. - let users = self.users.read().await; - println!( - "Source {}/{:?} has {} speaking.", - data.ssrc, - users.get(&data.ssrc), - if data.speaking {"started"} else {"stopped"}, - ); - }, - Ctx::VoicePacket(data) => { - // An event which fires for every received audio packet, - // containing the decoded data. - if let Some(audio) = data.audio { - // println!("Audio packet's first 5 samples: {:?}", audio.get(..5.min(audio.len()))); - // println!( - // "Audio packet sequence {:05} has {:04} bytes (decompressed from {}), SSRC {}", - // data.packet.sequence.0, - // audio.len() * std::mem::size_of::(), - // data.packet.payload.len(), - // data.packet.ssrc, - // ); - } else { - // println!("RTP packet, but no audio. Driver may not be configured to decode."); - } - }, - Ctx::RtcpPacket(data) => { - // An event which fires for every received rtcp packet, - // containing the call statistics and reporting information. - // println!("RTCP packet received: {:?}", data.packet); - }, - Ctx::ClientDisconnect( - ClientDisconnect {user_id, ..} - ) => { - // You can implement your own logic here to handle a user who has left the - // voice channel e.g., finalise processing of statistics etc. - // You will typically need to map the User ID to their SSRC; observed when - // first speaking. + use EventContext as Ctx; + match ctx { + Ctx::SpeakingStateUpdate(Speaking { + speaking, + ssrc, + user_id, + .. + }) => { + // Discord voice calls use RTP, where every sender uses a randomly allocated + // *Synchronisation Source* (SSRC) to allow receivers to tell which audio + // stream a received packet belongs to. As this number is not derived from + // the sender's user_id, only Discord Voice Gateway messages like this one + // inform us about which random SSRC a user has been allocated. Future voice + // packets will contain *only* the SSRC. + // + // You can implement logic here so that you can differentiate users' + // SSRCs and map the SSRC to the User ID and maintain this state. + // Using this map, you can map the `ssrc` in `voice_packet` + // to the user ID and handle their audio packets separately. + { + let data = self.data.write().await; + let mut users = data.users.write().await; + users.insert(ssrc.clone(), user_id.clone()); + } + println!( + "\n\n\nSpeaking state update: user {:?} has SSRC {:?}, using {:?}\n\n\n", + user_id, ssrc, speaking, + ); + } + Ctx::SpeakingUpdate(data) => { + // You can implement logic here which reacts to a user starting + // or stopping speaking, and to map their SSRC to User ID. + let vcdata = self.data.read().await; + let users = vcdata.users.read().await; + println!( + "Source {}/{:?} has {} speaking.", + data.ssrc, + users.get(&data.ssrc), + if data.speaking { "started" } else { "stopped" }, + ); + } + Ctx::VoicePacket(data) => { + // An event which fires for every received audio packet, + // containing the decoded data. + if let Some(audio) = data.audio { + // println!("Audio packet's first 5 samples: {:?}", audio.get(..5.min(audio.len()))); + // println!( + // "Audio packet sequence {:05} has {:04} bytes (decompressed from {}), SSRC {}", + // data.packet.sequence.0, + // audio.len() * std::mem::size_of::(), + // data.packet.payload.len(), + // data.packet.ssrc, + // ); + } else { + // println!("RTP packet, but no audio. Driver may not be configured to decode."); + } + } + Ctx::RtcpPacket(data) => { + // An event which fires for every received rtcp packet, + // containing the call statistics and reporting information. + // println!("RTCP packet received: {:?}", data.packet); + } + Ctx::ClientDisconnect(ClientDisconnect { user_id, .. }) => { + // You can implement your own logic here to handle a user who has left the + // voice channel e.g., finalise processing of statistics etc. + // You will typically need to map the User ID to their SSRC; observed when + // first speaking. - println!("Client disconnected: user {:?}", user_id); - }, - Ctx::DriverConnect( - ConnectData { channel_id, ..} - ) => { - println!("VoiceDriver is connected."); - }, - _ => { - // We won't be registering this struct for any more event classes. - unimplemented!() + println!("Client disconnected: user {:?}", user_id); + } + Ctx::DriverConnect(ConnectData { channel_id, .. }) => { + println!("VoiceDriver is connected."); + } + Ctx::DriverDisconnect(DisconnectData { + channel_id, + guild_id, + .. + }) => { + // TODO: Remove data from GuildVoiceData } - } + _ => { + // We won't be registering this struct for any more event classes. + unimplemented!() + } + } - None + None } } -pub async fn playFile(call: Arc>, file: String) { +pub async fn play_file(call: Arc>, file: String) { let mut call = call.lock().await; let ff_src = ffmpeg(file).await.expect("Unable to find file."); let (audio, handle) = create_player(ff_src); @@ -132,54 +179,71 @@ pub async fn join(ctx: Context, guild: Guild, cid: ChannelId) -> Option { - let call_handle = call.clone(); - { - let mut call = call.lock().await; + Ok(_) => { + let vc_data: VCData = VCData::new( + CallLocation { + guild: guild.id, + channel: cid + }, VoiceData::new( + call.clone() + ) + ); + { + let data = ctx.data.read().await; + match data.get::() { + Some(vc_guild) => { + let mut vc_guild = vc_guild.write().await; + vc_guild.insert(CallLocation { + guild: guild.id, + channel: cid + }, vc_data.clone()); + } + None => { + println!("VoiceData for client hasn't been initialized"); + } + } + } + let call_handle = call.clone(); + { + let mut call = call.lock().await; - call.add_global_event( - CoreEvent::SpeakingStateUpdate.into(), - Receiver::new(call_handle.clone()), - ); + call.add_global_event( + CoreEvent::SpeakingStateUpdate.into(), + vc_data.clone() + ); - call.add_global_event( - CoreEvent::SpeakingUpdate.into(), - Receiver::new(call_handle.clone()), - ); + call.add_global_event( + CoreEvent::SpeakingUpdate.into(), + vc_data.clone() + ); - call.add_global_event( - CoreEvent::VoicePacket.into(), - Receiver::new(call_handle.clone()), - ); + call.add_global_event( + CoreEvent::VoicePacket.into(), + vc_data.clone() + ); - call.add_global_event( - CoreEvent::RtcpPacket.into(), - Receiver::new(call_handle.clone()), - ); + call.add_global_event( + CoreEvent::RtcpPacket.into(), + vc_data.clone() + ); - call.add_global_event( - CoreEvent::ClientDisconnect.into(), - Receiver::new(call_handle.clone()), - ); + call.add_global_event( + CoreEvent::ClientDisconnect.into(), + vc_data.clone() + ); - call.add_global_event( - CoreEvent::ClientDisconnect.into(), - Receiver::new(call_handle.clone()), - ); - - call.add_global_event( - CoreEvent::DriverConnect.into(), - Receiver::new(call_handle.clone()), - ); - } - let ding_src = - std::env::var("DING_SOUND").expect("DING not found in DING_SOUND"); - playFile(call_handle, ding_src).await; - return Some(call); - } - Err(_err) => { - println!("Error joining channel"); - } + call.add_global_event( + CoreEvent::DriverConnect.into(), + vc_data.clone() + ); + } + let ding_src = std::env::var("DING_SOUND").expect("DING not found in DING_SOUND"); + play_file(call_handle, ding_src).await; + return Some(call); + } + Err(_err) => { + println!("Error joining channel"); + } } None }