Almost ready
This commit is contained in:
		
							parent
							
								
									5154c220bf
								
							
						
					
					
						commit
						2e273cdcde
					
				
							
								
								
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | ||||
| patreon: rodabafilms | ||||
							
								
								
									
										27
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										27
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1249,7 +1249,7 @@ dependencies = [ | ||||
|  "shell-words", | ||||
|  "thiserror", | ||||
|  "tokio", | ||||
|  "zerocopy", | ||||
|  "zerocopy 0.6.4", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -2377,7 +2377,7 @@ dependencies = [ | ||||
|  "thiserror", | ||||
|  "time", | ||||
|  "tokio", | ||||
|  "zerocopy", | ||||
|  "zerocopy 0.7.5", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -3137,7 +3137,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9" | ||||
| dependencies = [ | ||||
|  "byteorder", | ||||
|  "zerocopy-derive", | ||||
|  "zerocopy-derive 0.6.4", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zerocopy" | ||||
| version = "0.7.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "870cdd4b8b867698aea998d95bcc06c1d75fe566267781ee6f5ae8c9c45a3930" | ||||
| dependencies = [ | ||||
|  "byteorder", | ||||
|  "zerocopy-derive 0.7.5", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -3151,6 +3161,17 @@ dependencies = [ | ||||
|  "syn 2.0.37", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zerocopy-derive" | ||||
| version = "0.7.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e9c6f95fa5657518b36c6784ba7cdd89e8bdf9a16e58266085248bfb950860c5" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.37", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zeroize" | ||||
| version = "1.3.0" | ||||
|  | ||||
							
								
								
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -13,14 +13,19 @@ dotenv = "0.15.0" | ||||
| env_logger = "0.10.0" | ||||
| lazy_static = { version = "1.4.0", optional = true } | ||||
| librespot = { version = "0.4.2", default-features = false } | ||||
| log = "0.4.17" | ||||
| reqwest = "0.11.18" | ||||
| log = "0.4.20" | ||||
| reqwest = "0.11.20" | ||||
| samplerate = "0.2.4" | ||||
| serde = "1.0.163" | ||||
| serde_json = "1.0.96" | ||||
| serenity = { version = "0.11.5", features = ["framework", "cache", "standard_framework"], default-features = false } | ||||
| serde = "1.0.188" | ||||
| serde_json = "1.0.107" | ||||
| serenity = { version = "0.11.6", features = ["framework", "cache", "standard_framework"], default-features = false } | ||||
| songbird = "0.3.2" | ||||
| thiserror = "1.0.40" | ||||
| time = "0.3.21" | ||||
| tokio = { version = "1.28.1", features = ["rt", "full"] } | ||||
| zerocopy = "0.6.1" | ||||
| thiserror = "1.0.48" | ||||
| time = "0.3.28" | ||||
| tokio = { version = "1.32.0", features = ["rt", "full"] } | ||||
| zerocopy = "0.7.5" | ||||
| 
 | ||||
| [profile.release] | ||||
| opt-level = 3 | ||||
| lto = true | ||||
| debug = true | ||||
| @ -1,3 +1,7 @@ | ||||
| pub mod stream; | ||||
| 
 | ||||
| use self::stream::Stream; | ||||
| 
 | ||||
| use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult}; | ||||
| use librespot::playback::convert::Converter; | ||||
| use librespot::playback::decoder::AudioPacket; | ||||
| @ -5,8 +9,6 @@ use log::error; | ||||
| use std::io::Write; | ||||
| use tokio::sync::mpsc::UnboundedSender; | ||||
| 
 | ||||
| use crate::player::stream::Stream; | ||||
| 
 | ||||
| pub enum SinkEvent { | ||||
|   Start, | ||||
|   Stop, | ||||
| @ -26,6 +28,8 @@ impl StreamSink { | ||||
| impl Sink for StreamSink { | ||||
|   fn start(&mut self) -> SinkResult<()> { | ||||
|     if let Err(why) = self.sender.send(SinkEvent::Start) { | ||||
|       // WARNING: Returning an error causes librespot-playback to exit the process with status 1
 | ||||
| 
 | ||||
|       error!("Failed to send start playback event: {why}"); | ||||
|       return Err(SinkError::ConnectionRefused(why.to_string())); | ||||
|     } | ||||
| @ -35,10 +39,14 @@ impl Sink for StreamSink { | ||||
| 
 | ||||
|   fn stop(&mut self) -> SinkResult<()> { | ||||
|     if let Err(why) = self.sender.send(SinkEvent::Stop) { | ||||
|       // WARNING: Returning an error causes librespot-playback to exit the process with status 1
 | ||||
| 
 | ||||
|       error!("Failed to send start playback event: {why}"); | ||||
|       return Err(SinkError::ConnectionRefused(why.to_string())); | ||||
|     } | ||||
| 
 | ||||
|     self.stream.flush().ok(); | ||||
| 
 | ||||
|     Ok(()) | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -5,8 +5,10 @@ use std::{ | ||||
| 
 | ||||
| use songbird::input::reader::MediaSource; | ||||
| 
 | ||||
| // TODO: Find optimal value
 | ||||
| const MAX_SIZE: usize = 1024 * 1024; | ||||
| /// The lower the value, the less latency
 | ||||
| ///
 | ||||
| /// Too low of a value results in unpredictable audio
 | ||||
| const MAX_SIZE: usize = 32 * 1024; | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct Stream { | ||||
| @ -26,15 +28,19 @@ impl Read for Stream { | ||||
|     let (mutex, condvar) = &*self.inner; | ||||
|     let mut buffer = mutex.lock().expect("Mutex was poisoned"); | ||||
| 
 | ||||
|     log::trace!("Read!"); | ||||
|     // Prevent Discord jitter by filling buffer with zeroes if we don't have any audio
 | ||||
|     // (i.e. when you skip too far ahead in a song which hasn't been downloaded yet)
 | ||||
|     if buffer.is_empty() { | ||||
|       buf.fill(0); | ||||
|       condvar.notify_all(); | ||||
| 
 | ||||
|     while buffer.is_empty() { | ||||
|       buffer = condvar.wait(buffer).expect("Mutex was poisoned"); | ||||
|       return Ok(buf.len()); | ||||
|     } | ||||
| 
 | ||||
|     let max_read = usize::min(buf.len(), buffer.len()); | ||||
|     buf[0..max_read].copy_from_slice(&buffer[0..max_read]); | ||||
|     buffer.drain(0..max_read); | ||||
|     condvar.notify_all(); | ||||
| 
 | ||||
|     Ok(max_read) | ||||
|   } | ||||
| @ -56,6 +62,12 @@ impl Write for Stream { | ||||
|   } | ||||
| 
 | ||||
|   fn flush(&mut self) -> std::io::Result<()> { | ||||
|     let (mutex, condvar) = &*self.inner; | ||||
|     let mut buffer = mutex.lock().expect("Mutex was poisoned"); | ||||
| 
 | ||||
|     buffer.clear(); | ||||
|     condvar.notify_all(); | ||||
| 
 | ||||
|     Ok(()) | ||||
|   } | ||||
| } | ||||
| @ -25,7 +25,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO | ||||
|                   author | ||||
|                     .name("Maintained by: RoDaBaFilms") | ||||
|                     .url("https://rodabafilms.com/") | ||||
|                     .icon_url("https://rodabafilms.com/logo_2021_nobg.png") | ||||
|                     .icon_url("https://cdn.discordapp.com/avatars/389786424142200835/6bfe3840b0aa6a1baf432bb251b70c9f.webp?size=128") | ||||
|                 }) | ||||
|                 .description(format!("Current version: {}\n\nSpoticord is open source, check out [our GitHub](https://github.com/SpoticordMusic)", VERSION)) | ||||
|                 .color(Status::Info as u64) | ||||
|  | ||||
| @ -338,7 +338,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO | ||||
|         .title("Connected to voice channel") | ||||
|         .icon_url("https://spoticord.com/speaker.png") | ||||
|         .description(format!("Come listen along in <#{}>", channel_id)) | ||||
|         .footer("Spotify will automatically start playing on Spoticord") | ||||
|         .footer("You must manually go to Spotify and select your device") | ||||
|         .status(Status::Info) | ||||
|         .build(), | ||||
|     ) | ||||
|  | ||||
| @ -91,6 +91,7 @@ async fn main() { | ||||
|         _ = tokio::signal::ctrl_c() => { | ||||
|           info!("Received interrupt signal, shutting down..."); | ||||
| 
 | ||||
|           session_manager.shutdown().await; | ||||
|           shard_manager.lock().await.shutdown_all().await; | ||||
| 
 | ||||
|           break; | ||||
| @ -110,6 +111,7 @@ async fn main() { | ||||
|         }, if term.is_some() => { | ||||
|           info!("Received terminate signal, shutting down..."); | ||||
| 
 | ||||
|           session_manager.shutdown().await; | ||||
|           shard_manager.lock().await.shutdown_all().await; | ||||
| 
 | ||||
|           break; | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| pub mod stream; | ||||
| 
 | ||||
| use librespot::{ | ||||
|   connect::spirc::Spirc, | ||||
|   core::{config::ConnectConfig, session::Session}, | ||||
|   core::{ | ||||
|     config::{ConnectConfig, SessionConfig}, | ||||
|     session::Session, | ||||
|   }, | ||||
|   discovery::Credentials, | ||||
|   playback::{ | ||||
|     config::{Bitrate, PlayerConfig, VolumeCtrl}, | ||||
| @ -13,13 +14,11 @@ use librespot::{ | ||||
| use tokio::sync::mpsc::UnboundedReceiver; | ||||
| 
 | ||||
| use crate::{ | ||||
|   audio::{SinkEvent, StreamSink}, | ||||
|   audio::{stream::Stream, SinkEvent, StreamSink}, | ||||
|   librespot_ext::discovery::CredentialsExt, | ||||
|   utils, | ||||
| }; | ||||
| 
 | ||||
| use self::stream::Stream; | ||||
| 
 | ||||
| pub struct Player { | ||||
|   stream: Stream, | ||||
|   session: Option<Session>, | ||||
| @ -59,7 +58,16 @@ impl Player { | ||||
|     } | ||||
| 
 | ||||
|     // Connect the session
 | ||||
|     let (session, _) = Session::connect(Default::default(), credentials, None, false).await?; | ||||
|     let (session, _) = Session::connect( | ||||
|       SessionConfig { | ||||
|         ap_port: Some(9999), // Force the use of ap.spotify.com, which has the lowest latency
 | ||||
|         ..Default::default() | ||||
|       }, | ||||
|       credentials, | ||||
|       None, | ||||
|       false, | ||||
|     ) | ||||
|     .await?; | ||||
|     self.session = Some(session.clone()); | ||||
| 
 | ||||
|     let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { | ||||
|  | ||||
| @ -115,6 +115,10 @@ impl InnerSessionManager { | ||||
| 
 | ||||
|     count | ||||
|   } | ||||
| 
 | ||||
|   pub fn sessions(&self) -> Vec<SpoticordSession> { | ||||
|     self.sessions.values().cloned().collect() | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| impl SessionManager { | ||||
| @ -183,4 +187,13 @@ impl SessionManager { | ||||
|   pub async fn get_active_session_count(&self) -> usize { | ||||
|     self.0.read().await.get_active_session_count().await | ||||
|   } | ||||
| 
 | ||||
|   /// Tell all sessions to instantly shut down
 | ||||
|   pub async fn shutdown(&self) { | ||||
|     let sessions = self.0.read().await.sessions(); | ||||
| 
 | ||||
|     for session in sessions { | ||||
|       session.disconnect().await; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -30,8 +30,13 @@ use songbird::{ | ||||
|   tracks::TrackHandle, | ||||
|   Call, Event, EventContext, EventHandler, | ||||
| }; | ||||
| use std::{sync::Arc, time::Duration}; | ||||
| use tokio::sync::Mutex; | ||||
| use std::{ | ||||
|   io::Write, | ||||
|   ops::{Deref, DerefMut}, | ||||
|   sync::Arc, | ||||
|   time::Duration, | ||||
| }; | ||||
| use tokio::sync::{Mutex, RwLockReadGuard, RwLockWriteGuard}; | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>); | ||||
| @ -55,6 +60,8 @@ struct InnerSpoticordSession { | ||||
| 
 | ||||
|   spirc: Option<Spirc>, | ||||
| 
 | ||||
|   player: Option<Player>, | ||||
| 
 | ||||
|   /// Whether the session has been disconnected
 | ||||
|   /// If this is true then this instance should no longer be used and dropped
 | ||||
|   disconnected: bool, | ||||
| @ -97,6 +104,7 @@ impl SpoticordSession { | ||||
|       playback_info: None, | ||||
|       disconnect_handle: None, | ||||
|       spirc: None, | ||||
|       player: None, | ||||
|       disconnected: false, | ||||
|     }; | ||||
| 
 | ||||
| @ -132,14 +140,13 @@ impl SpoticordSession { | ||||
|       .clone(); | ||||
| 
 | ||||
|     { | ||||
|       let mut inner = self.0.write().await; | ||||
|       let mut inner = self.acquire_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; | ||||
|       let guild_id = self.acquire_read().await.guild_id; | ||||
|       session_manager.set_owner(owner_id, guild_id).await; | ||||
|     } | ||||
| 
 | ||||
|     // Create the player
 | ||||
| @ -150,28 +157,28 @@ impl SpoticordSession { | ||||
| 
 | ||||
|   /// Advance to the next track
 | ||||
|   pub async fn next(&mut self) { | ||||
|     if let Some(ref spirc) = self.0.read().await.spirc { | ||||
|     if let Some(ref spirc) = self.acquire_read().await.spirc { | ||||
|       spirc.next(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Rewind to the previous track
 | ||||
|   pub async fn previous(&mut self) { | ||||
|     if let Some(ref spirc) = self.0.read().await.spirc { | ||||
|     if let Some(ref spirc) = self.acquire_read().await.spirc { | ||||
|       spirc.prev(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Pause the current track
 | ||||
|   pub async fn pause(&mut self) { | ||||
|     if let Some(ref spirc) = self.0.read().await.spirc { | ||||
|     if let Some(ref spirc) = self.acquire_read().await.spirc { | ||||
|       spirc.pause(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Resume the current track
 | ||||
|   pub async fn resume(&mut self) { | ||||
|     if let Some(ref spirc) = self.0.read().await.spirc { | ||||
|     if let Some(ref spirc) = self.acquire_read().await.spirc { | ||||
|       spirc.play(); | ||||
|     } | ||||
|   } | ||||
| @ -237,8 +244,7 @@ impl SpoticordSession { | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     // Handle IPC packets
 | ||||
|     // This will automatically quit once the IPC connection is closed
 | ||||
|     // Handle events
 | ||||
|     tokio::spawn({ | ||||
|       let track = track_handle.clone(); | ||||
|       let ctx = ctx.clone(); | ||||
| @ -367,126 +373,16 @@ impl SpoticordSession { | ||||
|                 } | ||||
| 
 | ||||
|                 SinkEvent::Stop => { | ||||
|                   check_result(track.pause()); | ||||
|                   // EXPERIMENT: It may be beneficial to *NOT* pause songbird here
 | ||||
|                   // We already have a fallback if no audio is present in the buffer (write all zeroes aka silence)
 | ||||
|                   // So commenting this out may help prevent a substantial portion of jitter
 | ||||
|                   // This comes at a cost of more bandwidth, though opus should compress it down to almost nothing
 | ||||
| 
 | ||||
|                   // check_result(track.pause());
 | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           }; | ||||
| 
 | ||||
|           // match event {
 | ||||
|           //   // Session connect error
 | ||||
|           //   IpcPacket::ConnectError(why) => {
 | ||||
|           //     error!("Failed to connect to Spotify: {:?}", why);
 | ||||
| 
 | ||||
|           //     // Notify the user in the text channel
 | ||||
|           //     if let Err(why) = instance
 | ||||
|           //       .text_channel_id()
 | ||||
|           //       .await
 | ||||
|           //       .send_message(&instance.http().await, |message| {
 | ||||
|           //         message.embed(|embed| {
 | ||||
|           //           embed.title("Failed to connect to Spotify");
 | ||||
|           //           embed.description(why);
 | ||||
|           //           embed.footer(|footer| footer.text("Please try again"));
 | ||||
|           //           embed.color(Status::Error as u64);
 | ||||
| 
 | ||||
|           //           embed
 | ||||
|           //         });
 | ||||
| 
 | ||||
|           //         message
 | ||||
|           //       })
 | ||||
|           //       .await
 | ||||
|           //     {
 | ||||
|           //       error!("Failed to send error message: {:?}", why);
 | ||||
|           //     }
 | ||||
| 
 | ||||
|           //     break;
 | ||||
|           //   }
 | ||||
| 
 | ||||
|           //   // Sink requests playback to start/resume
 | ||||
|           //   IpcPacket::StartPlayback => {
 | ||||
|           //     check_result(track.play());
 | ||||
|           //   }
 | ||||
| 
 | ||||
|           //   // Sink requests playback to pause
 | ||||
|           //   IpcPacket::StopPlayback => {
 | ||||
|           //     check_result(track.pause());
 | ||||
|           //   }
 | ||||
| 
 | ||||
|           //   // A new track has been set by the player
 | ||||
|           //   IpcPacket::TrackChange(track) => {
 | ||||
|           //     // Convert to SpotifyId
 | ||||
|           //     let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri");
 | ||||
| 
 | ||||
|           //     let instance = instance.clone();
 | ||||
|           //     let ctx = ctx.clone();
 | ||||
| 
 | ||||
|           //     // Fetch track info
 | ||||
|           //     // This is done in a separate task to avoid blocking the IPC handler
 | ||||
|           //     tokio::spawn(async move {
 | ||||
|           //       if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await {
 | ||||
|           //         error!("Failed to update track: {:?}", why);
 | ||||
| 
 | ||||
|           //         instance.player_stopped().await;
 | ||||
|           //       }
 | ||||
|           //     });
 | ||||
|           //   }
 | ||||
| 
 | ||||
|           //   // The player has started playing a track
 | ||||
|           //   IpcPacket::Playing(track, position_ms, duration_ms) => {
 | ||||
|           //     // Convert to SpotifyId
 | ||||
|           //     let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri");
 | ||||
| 
 | ||||
|           //     let was_none = instance
 | ||||
|           //       .update_playback(duration_ms, position_ms, true)
 | ||||
|           //       .await;
 | ||||
| 
 | ||||
|           //     if was_none {
 | ||||
|           //       // Stop player if update track fails
 | ||||
|           //       if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await {
 | ||||
|           //         error!("Failed to update track: {:?}", why);
 | ||||
| 
 | ||||
|           //         instance.player_stopped().await;
 | ||||
|           //         return;
 | ||||
|           //       }
 | ||||
|           //     }
 | ||||
|           //   }
 | ||||
| 
 | ||||
|           //   IpcPacket::Paused(track, position_ms, duration_ms) => {
 | ||||
|           //     instance.start_disconnect_timer().await;
 | ||||
| 
 | ||||
|           //     // Convert to SpotifyId
 | ||||
|           //     let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri");
 | ||||
| 
 | ||||
|           //     let was_none = instance
 | ||||
|           //       .update_playback(duration_ms, position_ms, false)
 | ||||
|           //       .await;
 | ||||
| 
 | ||||
|           //     if was_none {
 | ||||
|           //       // Stop player if update track fails
 | ||||
| 
 | ||||
|           //       if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await {
 | ||||
|           //         error!("Failed to update track: {:?}", why);
 | ||||
| 
 | ||||
|           //         instance.player_stopped().await;
 | ||||
|           //         return;
 | ||||
|           //       }
 | ||||
|           //     }
 | ||||
|           //   }
 | ||||
| 
 | ||||
|           //   IpcPacket::Stopped => {
 | ||||
|           //     check_result(track.pause());
 | ||||
| 
 | ||||
|           //     {
 | ||||
|           //       let mut inner = inner.write().await;
 | ||||
|           //       inner.playback_info.take();
 | ||||
|           //     }
 | ||||
| 
 | ||||
|           //     instance.start_disconnect_timer().await;
 | ||||
|           //   }
 | ||||
| 
 | ||||
|           //   // Ignore other packets
 | ||||
|           //   _ => {}
 | ||||
|           // }
 | ||||
|         } | ||||
| 
 | ||||
|         // Clean up session
 | ||||
| @ -497,9 +393,10 @@ impl SpoticordSession { | ||||
|     }); | ||||
| 
 | ||||
|     // Update inner client and track
 | ||||
|     let mut inner = self.0.write().await; | ||||
|     let mut inner = self.acquire_write().await; | ||||
|     inner.track = Some(track_handle); | ||||
|     inner.spirc = Some(spirc); | ||||
|     inner.player = Some(player); | ||||
| 
 | ||||
|     Ok(()) | ||||
|   } | ||||
| @ -566,7 +463,7 @@ impl SpoticordSession { | ||||
|     } | ||||
| 
 | ||||
|     // Update track/episode
 | ||||
|     let mut inner = self.0.write().await; | ||||
|     let mut inner = self.acquire_write().await; | ||||
| 
 | ||||
|     if let Some(pbi) = inner.playback_info.as_mut() { | ||||
|       pbi.update_track_episode(spotify_id, track, episode); | ||||
| @ -577,7 +474,7 @@ impl SpoticordSession { | ||||
| 
 | ||||
|   /// Called when the player must stop, but not leave the call
 | ||||
|   async fn player_stopped(&self) { | ||||
|     let mut inner = self.0.write().await; | ||||
|     let mut inner = self.acquire_write().await; | ||||
| 
 | ||||
|     if let Some(spirc) = inner.spirc.take() { | ||||
|       spirc.shutdown(); | ||||
| @ -617,7 +514,7 @@ impl SpoticordSession { | ||||
|     //  read lock to read the current owner.
 | ||||
|     // This would deadlock if we have an active write lock
 | ||||
|     { | ||||
|       let mut inner = self.0.write().await; | ||||
|       let mut inner = self.acquire_write().await; | ||||
|       inner.disconnect_no_abort().await; | ||||
|     } | ||||
| 
 | ||||
| @ -633,7 +530,7 @@ impl SpoticordSession { | ||||
|     }; | ||||
| 
 | ||||
|     { | ||||
|       let mut inner = self.0.write().await; | ||||
|       let mut inner = self.acquire_write().await; | ||||
| 
 | ||||
|       if is_none { | ||||
|         inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); | ||||
| @ -659,8 +556,8 @@ impl SpoticordSession { | ||||
|   async fn start_disconnect_timer(&self) { | ||||
|     self.stop_disconnect_timer().await; | ||||
| 
 | ||||
|     let inner_arc = self.0.clone(); | ||||
|     let mut inner = inner_arc.write().await; | ||||
|     let arc_handle = self.0.clone(); | ||||
|     let mut inner = self.acquire_write().await; | ||||
| 
 | ||||
|     // Check if we are already disconnected
 | ||||
|     if inner.disconnected { | ||||
| @ -668,7 +565,7 @@ impl SpoticordSession { | ||||
|     } | ||||
| 
 | ||||
|     inner.disconnect_handle = Some(tokio::spawn({ | ||||
|       let inner = inner_arc.clone(); | ||||
|       let inner = arc_handle.clone(); | ||||
|       let instance = self.clone(); | ||||
| 
 | ||||
|       async move { | ||||
| @ -705,7 +602,7 @@ impl SpoticordSession { | ||||
| 
 | ||||
|   /// Stop the disconnect timer (if one is running)
 | ||||
|   async fn stop_disconnect_timer(&self) { | ||||
|     let mut inner = self.0.write().await; | ||||
|     let mut inner = self.acquire_write().await; | ||||
|     if let Some(handle) = inner.disconnect_handle.take() { | ||||
|       handle.abort(); | ||||
|     } | ||||
| @ -714,7 +611,7 @@ impl SpoticordSession { | ||||
|   /// Disconnect from the VC and send a message to the text channel
 | ||||
|   pub async fn disconnect_with_message(&self, content: &str) { | ||||
|     { | ||||
|       let mut inner = self.0.write().await; | ||||
|       let mut inner = self.acquire_write().await; | ||||
| 
 | ||||
|       // Firstly we disconnect
 | ||||
|       inner.disconnect_no_abort().await; | ||||
| @ -745,48 +642,97 @@ impl SpoticordSession { | ||||
| 
 | ||||
|   /// Get the owner
 | ||||
|   pub async fn owner(&self) -> Option<UserId> { | ||||
|     self.0.read().await.owner | ||||
|     self.acquire_read().await.owner | ||||
|   } | ||||
| 
 | ||||
|   /// Get the session manager
 | ||||
|   pub async fn session_manager(&self) -> SessionManager { | ||||
|     self.0.read().await.session_manager.clone() | ||||
|     self.acquire_read().await.session_manager.clone() | ||||
|   } | ||||
| 
 | ||||
|   /// Get the guild id
 | ||||
|   pub async fn guild_id(&self) -> GuildId { | ||||
|     self.0.read().await.guild_id | ||||
|     self.acquire_read().await.guild_id | ||||
|   } | ||||
| 
 | ||||
|   /// Get the channel id
 | ||||
|   pub async fn channel_id(&self) -> ChannelId { | ||||
|     self.0.read().await.channel_id | ||||
|     self.acquire_read().await.channel_id | ||||
|   } | ||||
| 
 | ||||
|   /// Get the channel id
 | ||||
|   #[allow(dead_code)] | ||||
|   pub async fn text_channel_id(&self) -> ChannelId { | ||||
|     self.0.read().await.text_channel_id | ||||
|     self.acquire_read().await.text_channel_id | ||||
|   } | ||||
| 
 | ||||
|   /// Get the playback info
 | ||||
|   pub async fn playback_info(&self) -> Option<PlaybackInfo> { | ||||
|     self.0.read().await.playback_info.clone() | ||||
|     self.acquire_read().await.playback_info.clone() | ||||
|   } | ||||
| 
 | ||||
|   pub async fn call(&self) -> Arc<Mutex<Call>> { | ||||
|     self.0.read().await.call.clone() | ||||
|     self.acquire_read().await.call.clone() | ||||
|   } | ||||
| 
 | ||||
|   #[allow(dead_code)] | ||||
|   pub async fn http(&self) -> Arc<Http> { | ||||
|     self.0.read().await.http.clone() | ||||
|     self.acquire_read().await.http.clone() | ||||
|   } | ||||
| 
 | ||||
|   async fn acquire_read(&self) -> ReadLock { | ||||
|     ReadLock(self.0.read().await) | ||||
|   } | ||||
| 
 | ||||
|   async fn acquire_write(&self) -> WriteLock { | ||||
|     WriteLock(self.0.write().await) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| struct ReadLock<'a>(RwLockReadGuard<'a, InnerSpoticordSession>); | ||||
| 
 | ||||
| impl<'a> Deref for ReadLock<'a> { | ||||
|   type Target = RwLockReadGuard<'a, InnerSpoticordSession>; | ||||
| 
 | ||||
|   #[inline] | ||||
|   fn deref(&self) -> &Self::Target { | ||||
|     &self.0 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| impl<'a> DerefMut for ReadLock<'a> { | ||||
|   #[inline] | ||||
|   fn deref_mut(&mut self) -> &mut Self::Target { | ||||
|     &mut self.0 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| struct WriteLock<'a>(RwLockWriteGuard<'a, InnerSpoticordSession>); | ||||
| 
 | ||||
| impl<'a> Deref for WriteLock<'a> { | ||||
|   type Target = RwLockWriteGuard<'a, InnerSpoticordSession>; | ||||
| 
 | ||||
|   #[inline] | ||||
|   fn deref(&self) -> &Self::Target { | ||||
|     &self.0 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| impl<'a> DerefMut for WriteLock<'a> { | ||||
|   #[inline] | ||||
|   fn deref_mut(&mut self) -> &mut Self::Target { | ||||
|     &mut self.0 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| impl InnerSpoticordSession { | ||||
|   /// Internal version of disconnect, which does not abort the disconnect timer
 | ||||
|   async fn disconnect_no_abort(&mut self) { | ||||
|     // Flush stream so that it is not permanently blocking the thread
 | ||||
|     if let Some(player) = self.player.take() { | ||||
|       player.get_stream().flush().ok(); | ||||
|     } | ||||
| 
 | ||||
|     self.disconnected = true; | ||||
|     self | ||||
|       .session_manager | ||||
| @ -813,12 +759,6 @@ impl InnerSpoticordSession { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| impl Drop for InnerSpoticordSession { | ||||
|   fn drop(&mut self) { | ||||
|     trace!("Dropping inner session"); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl EventHandler for SpoticordSession { | ||||
|   async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaXcess
						DaXcess