Rewrite SpoticordSession and small QoL updates
This commit is contained in:
		
							parent
							
								
									2223de1c30
								
							
						
					
					
						commit
						a53c885b6c
					
				| @ -1,2 +1,2 @@ | |||||||
| [target.x86_64-pc-windows-gnu] | [target.x86_64-pc-windows-gnu] | ||||||
| rustflags = "-C link-args=-lssp" # Does does compile without this line | rustflags = "-C link-args=-lssp" # Does not compile without this line | ||||||
							
								
								
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | patreon: rodabafilms | ||||||
| @ -1,4 +1,4 @@ | |||||||
| use log::{error, trace}; | use log::error; | ||||||
| use serenity::{ | use serenity::{ | ||||||
|   builder::CreateApplicationCommand, |   builder::CreateApplicationCommand, | ||||||
|   model::prelude::{interaction::application_command::ApplicationCommandInteraction, Channel}, |   model::prelude::{interaction::application_command::ApplicationCommandInteraction, Channel}, | ||||||
| @ -167,13 +167,13 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let data = ctx.data.read().await; |     let data = ctx.data.read().await; | ||||||
|     let mut session_manager = data.get::<SessionManager>().unwrap().clone(); |     let session_manager = data.get::<SessionManager>().unwrap().clone(); | ||||||
| 
 | 
 | ||||||
|     // Check if another session is already active in this server
 |     // Check if another session is already active in this server
 | ||||||
|     let mut session_opt = session_manager.get_session(guild.id).await; |     let mut session_opt = session_manager.get_session(guild.id).await; | ||||||
| 
 | 
 | ||||||
|     if let Some(session) = &session_opt { |     if let Some(session) = &session_opt { | ||||||
|       if let Some(owner) = session.get_owner().await { |       if let Some(owner) = session.owner().await { | ||||||
|         let msg = if owner == command.user.id { |         let msg = if owner == command.user.id { | ||||||
|           "You are already controlling the bot" |           "You are already controlling the bot" | ||||||
|         } else { |         } else { | ||||||
| @ -206,7 +206,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|           .description( |           .description( | ||||||
|           format!( |           format!( | ||||||
|             "You are already playing music in another server ({}).\nStop playing in that server first before joining this one.", |             "You are already playing music in another server ({}).\nStop playing in that server first before joining this one.", | ||||||
|             ctx.cache.guild(session.get_guild_id()).unwrap().name |             ctx.cache.guild(session.guild_id().await).unwrap().name | ||||||
|           )).status(Status::Error).build(), |           )).status(Status::Error).build(), | ||||||
|           true, |           true, | ||||||
|         ) |         ) | ||||||
| @ -218,8 +218,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|     defer_message(&ctx, &command, false).await; |     defer_message(&ctx, &command, false).await; | ||||||
| 
 | 
 | ||||||
|     if let Some(session) = &session_opt { |     if let Some(session) = &session_opt { | ||||||
|       trace!("{} != {}", session.get_channel_id(), channel_id); |       if session.channel_id().await != channel_id { | ||||||
|       if session.get_channel_id() != channel_id { |  | ||||||
|         session.disconnect().await; |         session.disconnect().await; | ||||||
|         session_opt = None; |         session_opt = None; | ||||||
| 
 | 
 | ||||||
| @ -228,7 +227,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if let Some(session) = &session_opt { |     if let Some(session) = session_opt.as_mut() { | ||||||
|       if let Err(why) = session.update_owner(&ctx, command.user.id).await { |       if let Err(why) = session.update_owner(&ctx, command.user.id).await { | ||||||
|         // Need to link first
 |         // Need to link first
 | ||||||
|         if let SessionCreateError::NoSpotifyError = why { |         if let SessionCreateError::NoSpotifyError = why { | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     if let Some(owner) = session.get_owner().await { |     if let Some(owner) = session.owner().await { | ||||||
|       if owner != command.user.id { |       if owner != command.user.id { | ||||||
|         // This message was generated by AI, and I love it.
 |         // This message was generated by AI, and I love it.
 | ||||||
|         respond_message( |         respond_message( | ||||||
|  | |||||||
| @ -48,7 +48,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let owner = match session.get_owner().await { |     let owner = match session.owner().await { | ||||||
|       Some(owner) => owner, |       Some(owner) => owner, | ||||||
|       None => { |       None => { | ||||||
|         not_playing.await; |         not_playing.await; | ||||||
| @ -58,7 +58,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // Get Playback Info from session
 |     // Get Playback Info from session
 | ||||||
|     let pbi = match session.get_playback_info().await { |     let pbi = match session.playback_info().await { | ||||||
|       Some(pbi) => pbi, |       Some(pbi) => pbi, | ||||||
|       None => { |       None => { | ||||||
|         not_playing.await; |         not_playing.await; | ||||||
|  | |||||||
| @ -2,14 +2,22 @@ use serde::{Deserialize, Serialize}; | |||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Debug, Clone)] | #[derive(Serialize, Deserialize, Debug, Clone)] | ||||||
| pub enum IpcPacket { | pub enum IpcPacket { | ||||||
|  |   /// Quit the player process
 | ||||||
|   Quit, |   Quit, | ||||||
| 
 | 
 | ||||||
|  |   /// Connect to Spotify with the given token and device name
 | ||||||
|   Connect(String, String), |   Connect(String, String), | ||||||
|  | 
 | ||||||
|  |   /// Disconnect from Spotify (unused)
 | ||||||
|   Disconnect, |   Disconnect, | ||||||
| 
 | 
 | ||||||
|  |   /// Unable to connect to Spotify
 | ||||||
|   ConnectError(String), |   ConnectError(String), | ||||||
| 
 | 
 | ||||||
|  |   /// The audio sink has started writing
 | ||||||
|   StartPlayback, |   StartPlayback, | ||||||
|  | 
 | ||||||
|  |   /// The audio sink has stopped writing
 | ||||||
|   StopPlayback, |   StopPlayback, | ||||||
| 
 | 
 | ||||||
|   /// The current Spotify track was changed
 |   /// The current Spotify track was changed
 | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| use std::time::Duration; | use std::{process::exit, time::Duration}; | ||||||
| 
 | 
 | ||||||
| use ipc_channel::ipc::{IpcError, TryRecvError}; | use ipc_channel::ipc::{IpcError, TryRecvError}; | ||||||
| use librespot::{ | use librespot::{ | ||||||
| @ -95,8 +95,8 @@ impl SpoticordPlayer { | |||||||
|     let (spirc, spirc_task) = Spirc::new( |     let (spirc, spirc_task) = Spirc::new( | ||||||
|       ConnectConfig { |       ConnectConfig { | ||||||
|         name: device_name.into(), |         name: device_name.into(), | ||||||
|         // 75%
 |         // 50%
 | ||||||
|         initial_volume: Some((65535 / 4) * 3), |         initial_volume: Some(65535 / 2), | ||||||
|         ..ConnectConfig::default() |         ..ConnectConfig::default() | ||||||
|       }, |       }, | ||||||
|       session.clone(), |       session.clone(), | ||||||
| @ -275,8 +275,7 @@ pub async fn main() { | |||||||
|       IpcPacket::Quit => { |       IpcPacket::Quit => { | ||||||
|         debug!("Received quit packet, exiting"); |         debug!("Received quit packet, exiting"); | ||||||
| 
 | 
 | ||||||
|         player.stop(); |         exit(0); | ||||||
|         break; |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       _ => { |       _ => { | ||||||
|  | |||||||
| @ -10,6 +10,9 @@ use super::SpoticordSession; | |||||||
| 
 | 
 | ||||||
| #[derive(Debug, Error)] | #[derive(Debug, Error)] | ||||||
| pub enum SessionCreateError { | pub enum SessionCreateError { | ||||||
|  |   #[error("This session has no owner assigned")] | ||||||
|  |   NoOwnerError, | ||||||
|  | 
 | ||||||
|   #[error("The user has not linked their Spotify account")] |   #[error("The user has not linked their Spotify account")] | ||||||
|   NoSpotifyError, |   NoSpotifyError, | ||||||
| 
 | 
 | ||||||
| @ -24,20 +27,22 @@ pub enum SessionCreateError { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct SessionManager { | pub struct SessionManager(Arc<tokio::sync::RwLock<InnerSessionManager>>); | ||||||
|   sessions: Arc<tokio::sync::RwLock<HashMap<GuildId, Arc<SpoticordSession>>>>, |  | ||||||
|   owner_map: Arc<tokio::sync::RwLock<HashMap<UserId, GuildId>>>, |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| impl TypeMapKey for SessionManager { | impl TypeMapKey for SessionManager { | ||||||
|   type Value = SessionManager; |   type Value = SessionManager; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl SessionManager { | pub struct InnerSessionManager { | ||||||
|   pub fn new() -> SessionManager { |   sessions: HashMap<GuildId, SpoticordSession>, | ||||||
|     SessionManager { |   owner_map: HashMap<UserId, GuildId>, | ||||||
|       sessions: Arc::new(tokio::sync::RwLock::new(HashMap::new())), | } | ||||||
|       owner_map: Arc::new(tokio::sync::RwLock::new(HashMap::new())), | 
 | ||||||
|  | impl InnerSessionManager { | ||||||
|  |   pub fn new() -> Self { | ||||||
|  |     Self { | ||||||
|  |       sessions: HashMap::new(), | ||||||
|  |       owner_map: HashMap::new(), | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -54,75 +59,60 @@ impl SessionManager { | |||||||
|     let session = |     let session = | ||||||
|       SpoticordSession::new(ctx, guild_id, channel_id, text_channel_id, owner_id).await?; |       SpoticordSession::new(ctx, guild_id, channel_id, text_channel_id, owner_id).await?; | ||||||
| 
 | 
 | ||||||
|     let mut sessions = self.sessions.write().await; |     self.sessions.insert(guild_id, session); | ||||||
|     let mut owner_map = self.owner_map.write().await; |     self.owner_map.insert(owner_id, guild_id); | ||||||
| 
 |  | ||||||
|     sessions.insert(guild_id, Arc::new(session)); |  | ||||||
|     owner_map.insert(owner_id, guild_id); |  | ||||||
| 
 | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Remove a session
 |   /// Remove a session
 | ||||||
|   pub async fn remove_session(&mut self, guild_id: GuildId) { |   pub async fn remove_session(&mut self, guild_id: GuildId) { | ||||||
|     let mut sessions = self.sessions.write().await; |     if let Some(session) = self.sessions.get(&guild_id) { | ||||||
| 
 |       if let Some(owner) = session.owner().await { | ||||||
|     if let Some(session) = sessions.get(&guild_id) { |         self.owner_map.remove(&owner); | ||||||
|       if let Some(owner) = session.get_owner().await { |  | ||||||
|         let mut owner_map = self.owner_map.write().await; |  | ||||||
|         owner_map.remove(&owner); |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     sessions.remove(&guild_id); |     self.sessions.remove(&guild_id); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Remove owner from owner map.
 |   /// Remove owner from owner map.
 | ||||||
|   /// Used whenever a user stops playing music without leaving the bot.
 |   /// Used whenever a user stops playing music without leaving the bot.
 | ||||||
|   pub async fn remove_owner(&mut self, owner_id: UserId) { |   pub fn remove_owner(&mut self, owner_id: UserId) { | ||||||
|     let mut owner_map = self.owner_map.write().await; |     self.owner_map.remove(&owner_id); | ||||||
|     owner_map.remove(&owner_id); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Set the owner of a session
 |   /// Set the owner of a session
 | ||||||
|   /// Used when a user joins a session that is already active
 |   /// Used when a user joins a session that is already active
 | ||||||
|   pub async fn set_owner(&mut self, owner_id: UserId, guild_id: GuildId) { |   pub fn set_owner(&mut self, owner_id: UserId, guild_id: GuildId) { | ||||||
|     let mut owner_map = self.owner_map.write().await; |     self.owner_map.insert(owner_id, guild_id); | ||||||
|     owner_map.insert(owner_id, guild_id); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Get a session by its guild ID
 |   /// Get a session by its guild ID
 | ||||||
|   pub async fn get_session(&self, guild_id: GuildId) -> Option<Arc<SpoticordSession>> { |   pub fn get_session(&self, guild_id: GuildId) -> Option<SpoticordSession> { | ||||||
|     let sessions = self.sessions.read().await; |     self.sessions.get(&guild_id).cloned() | ||||||
| 
 |  | ||||||
|     sessions.get(&guild_id).cloned() |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Find a Spoticord session by their current owner's ID
 |   /// Find a Spoticord session by their current owner's ID
 | ||||||
|   pub async fn find(&self, owner_id: UserId) -> Option<Arc<SpoticordSession>> { |   pub fn find(&self, owner_id: UserId) -> Option<SpoticordSession> { | ||||||
|     let sessions = self.sessions.read().await; |     let guild_id = self.owner_map.get(&owner_id)?; | ||||||
|     let owner_map = self.owner_map.read().await; |  | ||||||
| 
 | 
 | ||||||
|     let guild_id = owner_map.get(&owner_id)?; |     self.sessions.get(&guild_id).cloned() | ||||||
| 
 |  | ||||||
|     sessions.get(&guild_id).cloned() |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Get the amount of sessions
 |   /// Get the amount of sessions
 | ||||||
|   pub async fn get_session_count(&self) -> usize { |   pub fn get_session_count(&self) -> usize { | ||||||
|     let sessions = self.sessions.read().await; |     self.sessions.len() | ||||||
| 
 |  | ||||||
|     sessions.len() |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Get the amount of sessions with an owner
 |   /// Get the amount of sessions with an owner
 | ||||||
|   pub async fn get_active_session_count(&self) -> usize { |   pub async fn get_active_session_count(&self) -> usize { | ||||||
|     let sessions = self.sessions.read().await; |  | ||||||
| 
 |  | ||||||
|     let mut count: usize = 0; |     let mut count: usize = 0; | ||||||
| 
 | 
 | ||||||
|     for session in sessions.values() { |     for session in self.sessions.values() { | ||||||
|       if session.owner.read().await.is_some() { |       let session = session.0.read().await; | ||||||
|  | 
 | ||||||
|  |       if session.owner.is_some() { | ||||||
|         count += 1; |         count += 1; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @ -130,3 +120,65 @@ impl SessionManager { | |||||||
|     count |     count | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | impl SessionManager { | ||||||
|  |   pub fn new() -> Self { | ||||||
|  |     Self(Arc::new(tokio::sync::RwLock::new( | ||||||
|  |       InnerSessionManager::new(), | ||||||
|  |     ))) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Creates a new session for the given user in the given guild.
 | ||||||
|  |   pub async fn create_session( | ||||||
|  |     &self, | ||||||
|  |     ctx: &Context, | ||||||
|  |     guild_id: GuildId, | ||||||
|  |     channel_id: ChannelId, | ||||||
|  |     text_channel_id: ChannelId, | ||||||
|  |     owner_id: UserId, | ||||||
|  |   ) -> Result<(), SessionCreateError> { | ||||||
|  |     self | ||||||
|  |       .0 | ||||||
|  |       .write() | ||||||
|  |       .await | ||||||
|  |       .create_session(ctx, guild_id, channel_id, text_channel_id, owner_id) | ||||||
|  |       .await | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Remove a session
 | ||||||
|  |   pub async fn remove_session(&self, guild_id: GuildId) { | ||||||
|  |     self.0.write().await.remove_session(guild_id).await; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Remove owner from owner map.
 | ||||||
|  |   /// Used whenever a user stops playing music without leaving the bot.
 | ||||||
|  |   pub async fn remove_owner(&self, owner_id: UserId) { | ||||||
|  |     self.0.write().await.remove_owner(owner_id); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Set the owner of a session
 | ||||||
|  |   /// Used when a user joins a session that is already active
 | ||||||
|  |   pub async fn set_owner(&self, owner_id: UserId, guild_id: GuildId) { | ||||||
|  |     self.0.write().await.set_owner(owner_id, guild_id); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get a session by its guild ID
 | ||||||
|  |   pub async fn get_session(&self, guild_id: GuildId) -> Option<SpoticordSession> { | ||||||
|  |     self.0.read().await.get_session(guild_id) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Find a Spoticord session by their current owner's ID
 | ||||||
|  |   pub async fn find(&self, owner_id: UserId) -> Option<SpoticordSession> { | ||||||
|  |     self.0.read().await.find(owner_id) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get the amount of sessions
 | ||||||
|  |   pub async fn get_session_count(&self) -> usize { | ||||||
|  |     self.0.read().await.get_session_count() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get the amount of sessions with an owner
 | ||||||
|  |   pub async fn get_active_session_count(&self) -> usize { | ||||||
|  |     self.0.read().await.get_active_session_count().await | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -34,8 +34,10 @@ pub mod manager; | |||||||
| mod pbi; | mod pbi; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct SpoticordSession { | pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>); | ||||||
|   owner: Arc<RwLock<Option<UserId>>>, | 
 | ||||||
|  | struct InnerSpoticordSession { | ||||||
|  |   owner: Option<UserId>, | ||||||
|   guild_id: GuildId, |   guild_id: GuildId, | ||||||
|   channel_id: ChannelId, |   channel_id: ChannelId, | ||||||
|   text_channel_id: ChannelId, |   text_channel_id: ChannelId, | ||||||
| @ -45,13 +47,13 @@ pub struct SpoticordSession { | |||||||
|   session_manager: SessionManager, |   session_manager: SessionManager, | ||||||
| 
 | 
 | ||||||
|   call: Arc<Mutex<Call>>, |   call: Arc<Mutex<Call>>, | ||||||
|   track: TrackHandle, |   track: Option<TrackHandle>, | ||||||
| 
 | 
 | ||||||
|   playback_info: Arc<RwLock<Option<PlaybackInfo>>>, |   playback_info: Option<PlaybackInfo>, | ||||||
| 
 | 
 | ||||||
|   disconnect_handle: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>, |   disconnect_handle: Option<tokio::task::JoinHandle<()>>, | ||||||
| 
 | 
 | ||||||
|   client: Client, |   client: Option<Client>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl SpoticordSession { | impl SpoticordSession { | ||||||
| @ -64,9 +66,87 @@ impl SpoticordSession { | |||||||
|   ) -> Result<SpoticordSession, SessionCreateError> { |   ) -> Result<SpoticordSession, SessionCreateError> { | ||||||
|     // Get the Spotify token of the owner
 |     // Get the Spotify token of the owner
 | ||||||
|     let data = ctx.data.read().await; |     let data = ctx.data.read().await; | ||||||
|     let database = data.get::<Database>().unwrap(); |  | ||||||
|     let session_manager = data.get::<SessionManager>().unwrap().clone(); |     let session_manager = data.get::<SessionManager>().unwrap().clone(); | ||||||
| 
 | 
 | ||||||
|  |     // Join the voice channel
 | ||||||
|  |     let songbird = songbird::get(ctx).await.unwrap().clone(); | ||||||
|  | 
 | ||||||
|  |     let (call, result) = songbird.join(guild_id, channel_id).await; | ||||||
|  | 
 | ||||||
|  |     if let Err(why) = result { | ||||||
|  |       error!("Error joining voice channel: {:?}", why); | ||||||
|  |       return Err(SessionCreateError::JoinError(channel_id, guild_id)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let inner = InnerSpoticordSession { | ||||||
|  |       owner: Some(owner_id.clone()), | ||||||
|  |       guild_id, | ||||||
|  |       channel_id, | ||||||
|  |       text_channel_id, | ||||||
|  |       http: ctx.http.clone(), | ||||||
|  |       session_manager: session_manager.clone(), | ||||||
|  |       call: call.clone(), | ||||||
|  |       track: None, | ||||||
|  |       playback_info: None, | ||||||
|  |       disconnect_handle: None, | ||||||
|  |       client: None, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let mut instance = Self(Arc::new(RwLock::new(inner))); | ||||||
|  | 
 | ||||||
|  |     instance.create_player(ctx).await?; | ||||||
|  | 
 | ||||||
|  |     let mut call = call.lock().await; | ||||||
|  | 
 | ||||||
|  |     // Set up events
 | ||||||
|  |     call.add_global_event( | ||||||
|  |       songbird::Event::Core(songbird::CoreEvent::DriverDisconnect), | ||||||
|  |       instance.clone(), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     call.add_global_event( | ||||||
|  |       songbird::Event::Core(songbird::CoreEvent::ClientDisconnect), | ||||||
|  |       instance.clone(), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     Ok(instance) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   pub async fn update_owner( | ||||||
|  |     &mut self, | ||||||
|  |     ctx: &Context, | ||||||
|  |     owner_id: UserId, | ||||||
|  |   ) -> Result<(), SessionCreateError> { | ||||||
|  |     // Get the Spotify token of the owner
 | ||||||
|  |     let data = ctx.data.read().await; | ||||||
|  |     let session_manager = data.get::<SessionManager>().unwrap().clone(); | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       let mut inner = self.0.write().await; | ||||||
|  |       inner.owner = Some(owner_id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       let inner = self.0.clone(); | ||||||
|  |       let inner = inner.read().await; | ||||||
|  |       session_manager.set_owner(owner_id, inner.guild_id).await; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Create the player
 | ||||||
|  |     self.create_player(ctx).await?; | ||||||
|  | 
 | ||||||
|  |     Ok(()) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async fn create_player(&mut self, ctx: &Context) -> Result<(), SessionCreateError> { | ||||||
|  |     let owner_id = match self.owner().await.clone() { | ||||||
|  |       Some(owner_id) => owner_id, | ||||||
|  |       None => return Err(SessionCreateError::NoOwnerError), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let data = ctx.data.read().await; | ||||||
|  |     let database = data.get::<Database>().unwrap(); | ||||||
|  | 
 | ||||||
|     let token = match database.get_access_token(owner_id.to_string()).await { |     let token = match database.get_access_token(owner_id.to_string()).await { | ||||||
|       Ok(token) => token, |       Ok(token) => token, | ||||||
|       Err(why) => { |       Err(why) => { | ||||||
| @ -97,21 +177,15 @@ impl SpoticordSession { | |||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // Join the voice channel
 |  | ||||||
|     let songbird = songbird::get(ctx).await.unwrap().clone(); |  | ||||||
| 
 |  | ||||||
|     let (call, result) = songbird.join(guild_id, channel_id).await; |  | ||||||
| 
 |  | ||||||
|     if let Err(why) = result { |  | ||||||
|       error!("Error joining voice channel: {:?}", why); |  | ||||||
|       return Err(SessionCreateError::JoinError(channel_id, guild_id)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let mut call_mut = call.lock().await; |  | ||||||
| 
 |  | ||||||
|     // Spawn player process
 |     // Spawn player process
 | ||||||
|     let child = match Command::new(std::env::current_exe().unwrap()) |     let child = match Command::new(std::env::current_exe().unwrap()) | ||||||
|       .args(["--player", &tx_name, &rx_name]) |       .args([ | ||||||
|  |         "--player", | ||||||
|  |         &tx_name, | ||||||
|  |         &rx_name, | ||||||
|  |         "--debug-guild-id", | ||||||
|  |         &self.guild_id().await.to_string(), | ||||||
|  |       ]) | ||||||
|       .stdout(Stdio::piped()) |       .stdout(Stdio::piped()) | ||||||
|       .stderr(Stdio::inherit()) |       .stderr(Stdio::inherit()) | ||||||
|       .spawn() |       .spawn() | ||||||
| @ -141,32 +215,22 @@ impl SpoticordSession { | |||||||
|       create_player(Input::new(true, reader, Codec::Pcm, Container::Raw, None)); |       create_player(Input::new(true, reader, Codec::Pcm, Container::Raw, None)); | ||||||
|     track.pause(); |     track.pause(); | ||||||
| 
 | 
 | ||||||
|  |     let call = self.call().await; | ||||||
|  |     let mut call = call.lock().await; | ||||||
|  | 
 | ||||||
|     // Set call audio to track
 |     // Set call audio to track
 | ||||||
|     call_mut.play_only(track); |     call.play_only(track); | ||||||
| 
 |  | ||||||
|     let instance = Self { |  | ||||||
|       owner: Arc::new(RwLock::new(Some(owner_id.clone()))), |  | ||||||
|       guild_id, |  | ||||||
|       channel_id, |  | ||||||
|       text_channel_id, |  | ||||||
|       http: ctx.http.clone(), |  | ||||||
|       session_manager: session_manager.clone(), |  | ||||||
|       call: call.clone(), |  | ||||||
|       track: track_handle.clone(), |  | ||||||
|       playback_info: Arc::new(RwLock::new(None)), |  | ||||||
|       disconnect_handle: Arc::new(Mutex::new(None)), |  | ||||||
|       client: client.clone(), |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     // Clone variables for use in the IPC handler
 |  | ||||||
|     let ipc_track = track_handle.clone(); |  | ||||||
|     let ipc_client = client.clone(); |  | ||||||
|     let ipc_context = ctx.clone(); |  | ||||||
|     let mut ipc_instance = instance.clone(); |  | ||||||
| 
 | 
 | ||||||
|     // Handle IPC packets
 |     // Handle IPC packets
 | ||||||
|     // This will automatically quit once the IPC connection is closed
 |     // This will automatically quit once the IPC connection is closed
 | ||||||
|     tokio::spawn(async move { |     tokio::spawn({ | ||||||
|  |       let track = track_handle.clone(); | ||||||
|  |       let client = client.clone(); | ||||||
|  |       let ctx = ctx.clone(); | ||||||
|  |       let instance = self.clone(); | ||||||
|  |       let inner = self.0.clone(); | ||||||
|  | 
 | ||||||
|  |       async move { | ||||||
|         let check_result = |result| { |         let check_result = |result| { | ||||||
|           if let Err(why) = result { |           if let Err(why) = result { | ||||||
|             error!("Failed to issue track command: {:?}", why); |             error!("Failed to issue track command: {:?}", why); | ||||||
| @ -177,7 +241,7 @@ impl SpoticordSession { | |||||||
|           // Required for IpcPacket::TrackChange to work
 |           // Required for IpcPacket::TrackChange to work
 | ||||||
|           tokio::task::yield_now().await; |           tokio::task::yield_now().await; | ||||||
| 
 | 
 | ||||||
|         let msg = match ipc_client.try_recv() { |           let msg = match client.try_recv() { | ||||||
|             Ok(msg) => msg, |             Ok(msg) => msg, | ||||||
|             Err(why) => { |             Err(why) => { | ||||||
|               if let TryRecvError::Empty = why { |               if let TryRecvError::Empty = why { | ||||||
| @ -205,9 +269,10 @@ impl SpoticordSession { | |||||||
|               error!("Failed to connect to Spotify: {:?}", why); |               error!("Failed to connect to Spotify: {:?}", why); | ||||||
| 
 | 
 | ||||||
|               // Notify the user in the text channel
 |               // Notify the user in the text channel
 | ||||||
|             if let Err(why) = ipc_instance |               if let Err(why) = instance | ||||||
|               .text_channel_id |                 .text_channel_id() | ||||||
|               .send_message(&ipc_instance.http, |message| { |                 .await | ||||||
|  |                 .send_message(&instance.http().await, |message| { | ||||||
|                   message.embed(|embed| { |                   message.embed(|embed| { | ||||||
|                     embed.title("Failed to connect to Spotify"); |                     embed.title("Failed to connect to Spotify"); | ||||||
|                     embed.description(why); |                     embed.description(why); | ||||||
| @ -224,19 +289,19 @@ impl SpoticordSession { | |||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               // Clean up session
 |               // Clean up session
 | ||||||
|             ipc_instance.player_stopped().await; |               instance.player_stopped().await; | ||||||
| 
 | 
 | ||||||
|               break; |               break; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Sink requests playback to start/resume
 |             // Sink requests playback to start/resume
 | ||||||
|             IpcPacket::StartPlayback => { |             IpcPacket::StartPlayback => { | ||||||
|             check_result(ipc_track.play()); |               check_result(track.play()); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Sink requests playback to pause
 |             // Sink requests playback to pause
 | ||||||
|             IpcPacket::StopPlayback => { |             IpcPacket::StopPlayback => { | ||||||
|             check_result(ipc_track.pause()); |               check_result(track.pause()); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // A new track has been set by the player
 |             // A new track has been set by the player
 | ||||||
| @ -244,13 +309,13 @@ impl SpoticordSession { | |||||||
|               // Convert to SpotifyId
 |               // Convert to SpotifyId
 | ||||||
|               let track_id = SpotifyId::from_uri(&track).unwrap(); |               let track_id = SpotifyId::from_uri(&track).unwrap(); | ||||||
| 
 | 
 | ||||||
|             let mut instance = ipc_instance.clone(); |               let instance = instance.clone(); | ||||||
|             let context = ipc_context.clone(); |               let ctx = ctx.clone(); | ||||||
| 
 | 
 | ||||||
|               // Fetch track info
 |               // Fetch track info
 | ||||||
|               // This is done in a separate task to avoid blocking the IPC handler
 |               // This is done in a separate task to avoid blocking the IPC handler
 | ||||||
|               tokio::spawn(async move { |               tokio::spawn(async move { | ||||||
|               if let Err(why) = instance.update_track(&context, &owner_id, track_id).await { |                 if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { | ||||||
|                   error!("Failed to update track: {:?}", why); |                   error!("Failed to update track: {:?}", why); | ||||||
| 
 | 
 | ||||||
|                   instance.player_stopped().await; |                   instance.player_stopped().await; | ||||||
| @ -263,130 +328,73 @@ impl SpoticordSession { | |||||||
|               // Convert to SpotifyId
 |               // Convert to SpotifyId
 | ||||||
|               let track_id = SpotifyId::from_uri(&track).unwrap(); |               let track_id = SpotifyId::from_uri(&track).unwrap(); | ||||||
| 
 | 
 | ||||||
|             let was_none = ipc_instance |               let was_none = instance | ||||||
|                 .update_playback(duration_ms, position_ms, true) |                 .update_playback(duration_ms, position_ms, true) | ||||||
|                 .await; |                 .await; | ||||||
| 
 | 
 | ||||||
|               if was_none { |               if was_none { | ||||||
|                 // Stop player if update track fails
 |                 // Stop player if update track fails
 | ||||||
|               if let Err(why) = ipc_instance |                 if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { | ||||||
|                 .update_track(&ipc_context, &owner_id, track_id) |  | ||||||
|                 .await |  | ||||||
|               { |  | ||||||
|                   error!("Failed to update track: {:?}", why); |                   error!("Failed to update track: {:?}", why); | ||||||
| 
 | 
 | ||||||
|                 ipc_instance.player_stopped().await; |                   instance.player_stopped().await; | ||||||
|                   return; |                   return; | ||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             IpcPacket::Paused(track, position_ms, duration_ms) => { |             IpcPacket::Paused(track, position_ms, duration_ms) => { | ||||||
|             ipc_instance.start_disconnect_timer().await; |               instance.start_disconnect_timer().await; | ||||||
| 
 | 
 | ||||||
|               // Convert to SpotifyId
 |               // Convert to SpotifyId
 | ||||||
|               let track_id = SpotifyId::from_uri(&track).unwrap(); |               let track_id = SpotifyId::from_uri(&track).unwrap(); | ||||||
| 
 | 
 | ||||||
|             let was_none = ipc_instance |               let was_none = instance | ||||||
|                 .update_playback(duration_ms, position_ms, false) |                 .update_playback(duration_ms, position_ms, false) | ||||||
|                 .await; |                 .await; | ||||||
| 
 | 
 | ||||||
|               if was_none { |               if was_none { | ||||||
|                 // Stop player if update track fails
 |                 // Stop player if update track fails
 | ||||||
|               if let Err(why) = ipc_instance | 
 | ||||||
|                 .update_track(&ipc_context, &owner_id, track_id) |                 if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { | ||||||
|                 .await |  | ||||||
|               { |  | ||||||
|                   error!("Failed to update track: {:?}", why); |                   error!("Failed to update track: {:?}", why); | ||||||
| 
 | 
 | ||||||
|                 ipc_instance.player_stopped().await; |                   instance.player_stopped().await; | ||||||
|                   return; |                   return; | ||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             IpcPacket::Stopped => { |             IpcPacket::Stopped => { | ||||||
|             check_result(ipc_track.pause()); |               check_result(track.pause()); | ||||||
| 
 | 
 | ||||||
|             ipc_instance.playback_info.write().await.take(); |               let mut inner = inner.write().await; | ||||||
|             ipc_instance.start_disconnect_timer().await; |               inner.playback_info.take(); | ||||||
|  | 
 | ||||||
|  |               instance.start_disconnect_timer().await; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Ignore other packets
 |             // Ignore other packets
 | ||||||
|             _ => {} |             _ => {} | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Set up events
 |  | ||||||
|     call_mut.add_global_event( |  | ||||||
|       songbird::Event::Core(songbird::CoreEvent::DriverDisconnect), |  | ||||||
|       instance.clone(), |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     call_mut.add_global_event( |  | ||||||
|       songbird::Event::Core(songbird::CoreEvent::ClientDisconnect), |  | ||||||
|       instance.clone(), |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     // Inform the player process to connect to Spotify
 |     // Inform the player process to connect to Spotify
 | ||||||
|     if let Err(why) = client.send(IpcPacket::Connect(token, user.device_name)) { |     if let Err(why) = client.send(IpcPacket::Connect(token, user.device_name)) { | ||||||
|       error!("Failed to send IpcPacket::Connect packet: {:?}", why); |       error!("Failed to send IpcPacket::Connect packet: {:?}", why); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Ok(instance) |     // Update inner client and track
 | ||||||
|   } |     let mut inner = self.0.write().await; | ||||||
| 
 |     inner.track = Some(track_handle); | ||||||
|   pub async fn update_owner( |     inner.client = Some(client); | ||||||
|     &self, |  | ||||||
|     ctx: &Context, |  | ||||||
|     owner_id: UserId, |  | ||||||
|   ) -> Result<(), SessionCreateError> { |  | ||||||
|     // Get the Spotify token of the owner
 |  | ||||||
|     let data = ctx.data.read().await; |  | ||||||
|     let database = data.get::<Database>().unwrap(); |  | ||||||
|     let mut session_manager = data.get::<SessionManager>().unwrap().clone(); |  | ||||||
| 
 |  | ||||||
|     let token = match database.get_access_token(owner_id.to_string()).await { |  | ||||||
|       Ok(token) => token, |  | ||||||
|       Err(why) => { |  | ||||||
|         if let DatabaseError::InvalidStatusCode(code) = why { |  | ||||||
|           if code == 404 { |  | ||||||
|             return Err(SessionCreateError::NoSpotifyError); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return Err(SessionCreateError::DatabaseError); |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     let user = match database.get_user(owner_id.to_string()).await { |  | ||||||
|       Ok(user) => user, |  | ||||||
|       Err(why) => { |  | ||||||
|         error!("Failed to get user: {:?}", why); |  | ||||||
|         return Err(SessionCreateError::DatabaseError); |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     { |  | ||||||
|       let mut owner = self.owner.write().await; |  | ||||||
|       *owner = Some(owner_id); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     session_manager.set_owner(owner_id, self.guild_id).await; |  | ||||||
| 
 |  | ||||||
|     // Inform the player process to connect to Spotify
 |  | ||||||
|     if let Err(why) = self |  | ||||||
|       .client |  | ||||||
|       .send(IpcPacket::Connect(token, user.device_name)) |  | ||||||
|     { |  | ||||||
|       error!("Failed to send IpcPacket::Connect packet: {:?}", why); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Update current track
 |   /// Update current track
 | ||||||
|   async fn update_track( |   async fn update_track( | ||||||
|     &self, |     &self, | ||||||
|     ctx: &Context, |     ctx: &Context, | ||||||
| @ -394,9 +402,9 @@ impl SpoticordSession { | |||||||
|     spotify_id: SpotifyId, |     spotify_id: SpotifyId, | ||||||
|   ) -> Result<(), String> { |   ) -> Result<(), String> { | ||||||
|     let should_update = { |     let should_update = { | ||||||
|       let pbi = self.playback_info.read().await; |       let pbi = self.playback_info().await; | ||||||
| 
 | 
 | ||||||
|       if let Some(pbi) = &*pbi { |       if let Some(pbi) = pbi { | ||||||
|         pbi.spotify_id.is_none() || pbi.spotify_id.unwrap() != spotify_id |         pbi.spotify_id.is_none() || pbi.spotify_id.unwrap() != spotify_id | ||||||
|       } else { |       } else { | ||||||
|         false |         false | ||||||
| @ -447,9 +455,10 @@ impl SpoticordSession { | |||||||
|       episode = Some(episode_info); |       episode = Some(episode_info); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let mut pbi = self.playback_info.write().await; |     // Update track/episode
 | ||||||
|  |     let mut inner = self.0.write().await; | ||||||
| 
 | 
 | ||||||
|     if let Some(pbi) = &mut *pbi { |     if let Some(pbi) = inner.playback_info.as_mut() { | ||||||
|       pbi.update_track_episode(spotify_id, track, episode); |       pbi.update_track_episode(spotify_id, track, episode); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -457,74 +466,176 @@ impl SpoticordSession { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Called when the player must stop, but not leave the call
 |   /// Called when the player must stop, but not leave the call
 | ||||||
|   async fn player_stopped(&mut self) { |   async fn player_stopped(&self) { | ||||||
|     // Disconnect from Spotify
 |     let mut inner = self.0.write().await; | ||||||
|     if let Err(why) = self.client.send(IpcPacket::Disconnect) { | 
 | ||||||
|       error!("Failed to send disconnect packet: {:?}", why); |     if let Some(client) = inner.client.take() { | ||||||
|  |       // Ask player to quit (will cause defunct process)
 | ||||||
|  |       if let Err(why) = client.send(IpcPacket::Quit) { | ||||||
|  |         error!("Failed to send quit packet: {:?}", why); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* So this is really annoying, but necessary.
 |     if let Some(track) = inner.track.take() { | ||||||
|      * If we pause songbird too quickly, the player would still have |       // Stop the playback, and freeing the child handle, removing the defunct process
 | ||||||
|      *  some audio to write to stdout. Because songbird is paused, |       if let Err(why) = track.stop() { | ||||||
|      *  the audio is never read, and the player process will hang. |         error!("Failed to stop track: {:?}", why); | ||||||
|      * |       } | ||||||
|      * So yeah we just blatanly wait a second, and hope that's enough. |  | ||||||
|      * Most likely causes issues when the system is a bit slow. |  | ||||||
|      */ |  | ||||||
|     tokio::time::sleep(Duration::from_millis(1000)).await; |  | ||||||
| 
 |  | ||||||
|     if let Err(why) = self.track.pause() { |  | ||||||
|       error!("Failed to pause track: {:?}", why); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Clear owner
 |     // Clear owner
 | ||||||
|     let mut owner = self.owner.write().await; |     if let Some(owner_id) = inner.owner.take() { | ||||||
|     if let Some(owner_id) = owner.take() { |       inner.session_manager.remove_owner(owner_id).await; | ||||||
|       self.session_manager.remove_owner(owner_id).await; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Clear playback info
 |     // Clear playback info
 | ||||||
|     let mut playback_info = self.playback_info.write().await; |     inner.playback_info = None; | ||||||
|     *playback_info = None; | 
 | ||||||
|  |     // Unlock to prevent deadlock in start_disconnect_timer
 | ||||||
|  |     drop(inner); | ||||||
| 
 | 
 | ||||||
|     // Disconnect automatically after some time
 |     // Disconnect automatically after some time
 | ||||||
|     self.start_disconnect_timer().await; |     self.start_disconnect_timer().await; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Internal version of disconnect, which does not abort the disconnect timer
 |  | ||||||
|   async fn disconnect_no_abort(&self) { |  | ||||||
|     self |  | ||||||
|       .session_manager |  | ||||||
|       .clone() |  | ||||||
|       .remove_session(self.guild_id) |  | ||||||
|       .await; |  | ||||||
| 
 |  | ||||||
|     let mut call = self.call.lock().await; |  | ||||||
| 
 |  | ||||||
|     self.track.stop().unwrap_or(()); |  | ||||||
|     call.remove_all_global_events(); |  | ||||||
| 
 |  | ||||||
|     if let Err(why) = call.leave().await { |  | ||||||
|       error!("Failed to leave voice channel: {:?}", why); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // Disconnect from voice channel and remove session from manager
 |   // Disconnect from voice channel and remove session from manager
 | ||||||
|   pub async fn disconnect(&self) { |   pub async fn disconnect(&self) { | ||||||
|     info!("Disconnecting from voice channel {}", self.channel_id); |     info!( | ||||||
|  |       "[{}] Disconnecting from voice channel {}", | ||||||
|  |       self.guild_id().await, | ||||||
|  |       self.channel_id().await | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     self.disconnect_no_abort().await; |     // We must run disconnect_no_abort within a read lock
 | ||||||
|  |     // This is because `SessionManager::remove_session` will acquire a
 | ||||||
|  |     //  read lock to read the current owner.
 | ||||||
|  |     // This would deadlock if we have an active write lock
 | ||||||
|  |     { | ||||||
|  |       let inner = self.0.read().await; | ||||||
|  |       inner.disconnect_no_abort().await; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     // Stop the disconnect timer, if one is running
 |     // Stop the disconnect timer, if one is running
 | ||||||
|     let mut dc_handle = self.disconnect_handle.lock().await; |     let mut inner = self.0.write().await; | ||||||
| 
 |     if let Some(handle) = inner.disconnect_handle.take() { | ||||||
|     if let Some(handle) = dc_handle.take() { |  | ||||||
|       handle.abort(); |       handle.abort(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Disconnect from voice channel with a message
 |   // Update playback info (duration, position, playing state)
 | ||||||
|   pub async fn disconnect_with_message(&self, content: &str) { |   async fn update_playback(&self, duration_ms: u32, position_ms: u32, playing: bool) -> bool { | ||||||
|  |     let is_none = { | ||||||
|  |       let pbi = self.playback_info().await; | ||||||
|  | 
 | ||||||
|  |       pbi.is_none() | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let mut inner = self.0.write().await; | ||||||
|  | 
 | ||||||
|  |     if is_none { | ||||||
|  |       inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); | ||||||
|  |     } else { | ||||||
|  |       // Update position, duration and playback state
 | ||||||
|  |       inner | ||||||
|  |         .playback_info | ||||||
|  |         .as_mut() | ||||||
|  |         .unwrap() | ||||||
|  |         .update_pos_dur(position_ms, duration_ms, playing); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     is_none | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Start the disconnect timer, which will disconnect the bot from the voice channel after a
 | ||||||
|  |   /// certain amount of time
 | ||||||
|  |   async fn start_disconnect_timer(&self) { | ||||||
|  |     let inner_arc = self.0.clone(); | ||||||
|  |     let mut inner = inner_arc.write().await; | ||||||
|  | 
 | ||||||
|  |     // Abort the previous timer, if one is running
 | ||||||
|  |     if let Some(handle) = inner.disconnect_handle.take() { | ||||||
|  |       handle.abort(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     inner.disconnect_handle = Some(tokio::spawn({ | ||||||
|  |       let inner = inner_arc.clone(); | ||||||
|  | 
 | ||||||
|  |       async move { | ||||||
|  |         let mut timer = tokio::time::interval(Duration::from_secs(DISCONNECT_TIME)); | ||||||
|  | 
 | ||||||
|  |         // Ignore first (immediate) tick
 | ||||||
|  |         timer.tick().await; | ||||||
|  |         timer.tick().await; | ||||||
|  | 
 | ||||||
|  |         // Make sure this task has not been aborted, if it has this will automatically stop execution.
 | ||||||
|  |         tokio::task::yield_now().await; | ||||||
|  | 
 | ||||||
|  |         // Lock the inner mutex as late as possible to prevent any deadlocks
 | ||||||
|  |         let mut inner = inner.write().await; | ||||||
|  | 
 | ||||||
|  |         let is_playing = { | ||||||
|  |           if let Some(ref pbi) = inner.playback_info { | ||||||
|  |             pbi.is_playing | ||||||
|  |           } else { | ||||||
|  |             false | ||||||
|  |           } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if !is_playing { | ||||||
|  |           info!("Player is not playing, disconnecting"); | ||||||
|  |           inner | ||||||
|  |             .disconnect_with_message( | ||||||
|  |               "The player has been inactive for too long, and has been disconnected.", | ||||||
|  |             ) | ||||||
|  |             .await; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     })); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /* Inner getters */ | ||||||
|  | 
 | ||||||
|  |   /// Get the owner
 | ||||||
|  |   pub async fn owner(&self) -> Option<UserId> { | ||||||
|  |     self.0.read().await.owner | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get the session manager
 | ||||||
|  |   pub async fn session_manager(&self) -> SessionManager { | ||||||
|  |     self.0.read().await.session_manager.clone() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get the guild id
 | ||||||
|  |   pub async fn guild_id(&self) -> GuildId { | ||||||
|  |     self.0.read().await.guild_id | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get the channel id
 | ||||||
|  |   pub async fn channel_id(&self) -> ChannelId { | ||||||
|  |     self.0.read().await.channel_id | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get the channel id
 | ||||||
|  |   pub async fn text_channel_id(&self) -> ChannelId { | ||||||
|  |     self.0.read().await.text_channel_id | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get the playback info
 | ||||||
|  |   pub async fn playback_info(&self) -> Option<PlaybackInfo> { | ||||||
|  |     self.0.read().await.playback_info.clone() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   pub async fn call(&self) -> Arc<Mutex<Call>> { | ||||||
|  |     self.0.read().await.call.clone() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   pub async fn http(&self) -> Arc<Http> { | ||||||
|  |     self.0.read().await.http.clone() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl InnerSpoticordSession { | ||||||
|  |   pub async fn disconnect_with_message(&mut self, content: &str) { | ||||||
|     self.disconnect_no_abort().await; |     self.disconnect_no_abort().await; | ||||||
| 
 | 
 | ||||||
|     if let Err(why) = self |     if let Err(why) = self | ||||||
| @ -544,102 +655,32 @@ impl SpoticordSession { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Stop the disconnect timer, if one is running
 |     // Stop the disconnect timer, if one is running
 | ||||||
|     let mut dc_handle = self.disconnect_handle.lock().await; |     if let Some(handle) = self.disconnect_handle.take() { | ||||||
| 
 |  | ||||||
|     if let Some(handle) = dc_handle.take() { |  | ||||||
|       handle.abort(); |       handle.abort(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Update playback info (duration, position, playing state)
 |   /// Internal version of disconnect, which does not abort the disconnect timer
 | ||||||
|   async fn update_playback(&self, duration_ms: u32, position_ms: u32, playing: bool) -> bool { |   async fn disconnect_no_abort(&self) { | ||||||
|     let is_none = { |     self.session_manager.remove_session(self.guild_id).await; | ||||||
|       let pbi = self.playback_info.read().await; |  | ||||||
| 
 | 
 | ||||||
|       pbi.is_none() |     let mut call = self.call.lock().await; | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     if is_none { |     if let Some(ref track) = self.track { | ||||||
|       let mut pbi = self.playback_info.write().await; |       track.stop().unwrap_or(()); | ||||||
|       *pbi = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); |  | ||||||
|     } else { |  | ||||||
|       let mut pbi = self.playback_info.write().await; |  | ||||||
| 
 |  | ||||||
|       // Update position, duration and playback state
 |  | ||||||
|       pbi |  | ||||||
|         .as_mut() |  | ||||||
|         .unwrap() |  | ||||||
|         .update_pos_dur(position_ms, duration_ms, playing) |  | ||||||
|         .await; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     is_none |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   /// Start the disconnect timer, which will disconnect the bot from the voice channel after a
 |     call.remove_all_global_events(); | ||||||
|   /// certain amount of time
 |  | ||||||
|   async fn start_disconnect_timer(&self) { |  | ||||||
|     let pbi = self.playback_info.clone(); |  | ||||||
|     let instance = self.clone(); |  | ||||||
| 
 | 
 | ||||||
|     let mut handle = self.disconnect_handle.lock().await; |     if let Err(why) = call.leave().await { | ||||||
| 
 |       error!("Failed to leave voice channel: {:?}", why); | ||||||
|     // Abort the previous timer, if one is running
 |     } | ||||||
|     if let Some(handle) = handle.take() { |   } | ||||||
|       handle.abort(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|     *handle = Some(tokio::spawn(async move { | impl Drop for InnerSpoticordSession { | ||||||
|       let mut timer = tokio::time::interval(Duration::from_secs(DISCONNECT_TIME)); |   fn drop(&mut self) { | ||||||
| 
 |     trace!("Dropping inner session"); | ||||||
|       // Ignore first (immediate) tick
 |  | ||||||
|       timer.tick().await; |  | ||||||
|       timer.tick().await; |  | ||||||
| 
 |  | ||||||
|       // Make sure this task has not been aborted, if it has this will automatically stop execution.
 |  | ||||||
|       tokio::task::yield_now().await; |  | ||||||
| 
 |  | ||||||
|       let is_playing = { |  | ||||||
|         let pbi = pbi.read().await; |  | ||||||
| 
 |  | ||||||
|         if let Some(pbi) = &*pbi { |  | ||||||
|           pbi.is_playing |  | ||||||
|         } else { |  | ||||||
|           false |  | ||||||
|         } |  | ||||||
|       }; |  | ||||||
| 
 |  | ||||||
|       if !is_playing { |  | ||||||
|         info!("Player is not playing, disconnecting"); |  | ||||||
|         instance |  | ||||||
|           .disconnect_with_message( |  | ||||||
|             "The player has been inactive for too long, and has been disconnected.", |  | ||||||
|           ) |  | ||||||
|           .await; |  | ||||||
|       } |  | ||||||
|     })); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // Get the playback info for the current track
 |  | ||||||
|   pub async fn get_playback_info(&self) -> Option<PlaybackInfo> { |  | ||||||
|     self.playback_info.read().await.clone() |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // Get the current owner of this session
 |  | ||||||
|   pub async fn get_owner(&self) -> Option<UserId> { |  | ||||||
|     let owner = self.owner.read().await; |  | ||||||
| 
 |  | ||||||
|     *owner |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // Get the server id this session is playing in
 |  | ||||||
|   pub fn get_guild_id(&self) -> GuildId { |  | ||||||
|     self.guild_id |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // Get the channel id this session is playing in
 |  | ||||||
|   pub fn get_channel_id(&self) -> ChannelId { |  | ||||||
|     self.channel_id |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -654,11 +695,16 @@ impl EventHandler for SpoticordSession { | |||||||
|       EventContext::ClientDisconnect(who) => { |       EventContext::ClientDisconnect(who) => { | ||||||
|         trace!("Client disconnected, {}", who.user_id.to_string()); |         trace!("Client disconnected, {}", who.user_id.to_string()); | ||||||
| 
 | 
 | ||||||
|         if let Some(session) = self.session_manager.find(UserId(who.user_id.0)).await { |         if let Some(session) = self | ||||||
|           if session.get_guild_id() == self.guild_id && session.get_channel_id() == self.channel_id |           .session_manager() | ||||||
|  |           .await | ||||||
|  |           .find(UserId(who.user_id.0)) | ||||||
|  |           .await | ||||||
|         { |         { | ||||||
|             // Clone because haha immutable references
 |           if session.guild_id().await == self.guild_id().await | ||||||
|             self.clone().player_stopped().await; |             && session.channel_id().await == self.channel_id().await | ||||||
|  |           { | ||||||
|  |             self.player_stopped().await; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ impl PlaybackInfo { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Update position, duration and playback state
 |   /// Update position, duration and playback state
 | ||||||
|   pub async fn update_pos_dur(&mut self, position_ms: u32, duration_ms: u32, is_playing: bool) { |   pub fn update_pos_dur(&mut self, position_ms: u32, duration_ms: u32, is_playing: bool) { | ||||||
|     self.position_ms = position_ms; |     self.position_ms = position_ms; | ||||||
|     self.duration_ms = duration_ms; |     self.duration_ms = duration_ms; | ||||||
|     self.is_playing = is_playing; |     self.is_playing = is_playing; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaXcess
						DaXcess