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", |  "shell-words", | ||||||
|  "thiserror", |  "thiserror", | ||||||
|  "tokio", |  "tokio", | ||||||
|  "zerocopy", |  "zerocopy 0.6.4", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -2377,7 +2377,7 @@ dependencies = [ | |||||||
|  "thiserror", |  "thiserror", | ||||||
|  "time", |  "time", | ||||||
|  "tokio", |  "tokio", | ||||||
|  "zerocopy", |  "zerocopy 0.7.5", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -3137,7 +3137,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||||
| checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9" | checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "byteorder", |  "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]] | [[package]] | ||||||
| @ -3151,6 +3161,17 @@ dependencies = [ | |||||||
|  "syn 2.0.37", |  "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]] | [[package]] | ||||||
| name = "zeroize" | name = "zeroize" | ||||||
| version = "1.3.0" | version = "1.3.0" | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -13,14 +13,19 @@ dotenv = "0.15.0" | |||||||
| env_logger = "0.10.0" | env_logger = "0.10.0" | ||||||
| lazy_static = { version = "1.4.0", optional = true } | lazy_static = { version = "1.4.0", optional = true } | ||||||
| librespot = { version = "0.4.2", default-features = false } | librespot = { version = "0.4.2", default-features = false } | ||||||
| log = "0.4.17" | log = "0.4.20" | ||||||
| reqwest = "0.11.18" | reqwest = "0.11.20" | ||||||
| samplerate = "0.2.4" | samplerate = "0.2.4" | ||||||
| serde = "1.0.163" | serde = "1.0.188" | ||||||
| serde_json = "1.0.96" | serde_json = "1.0.107" | ||||||
| serenity = { version = "0.11.5", features = ["framework", "cache", "standard_framework"], default-features = false } | serenity = { version = "0.11.6", features = ["framework", "cache", "standard_framework"], default-features = false } | ||||||
| songbird = "0.3.2" | songbird = "0.3.2" | ||||||
| thiserror = "1.0.40" | thiserror = "1.0.48" | ||||||
| time = "0.3.21" | time = "0.3.28" | ||||||
| tokio = { version = "1.28.1", features = ["rt", "full"] } | tokio = { version = "1.32.0", features = ["rt", "full"] } | ||||||
| zerocopy = "0.6.1" | 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::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult}; | ||||||
| use librespot::playback::convert::Converter; | use librespot::playback::convert::Converter; | ||||||
| use librespot::playback::decoder::AudioPacket; | use librespot::playback::decoder::AudioPacket; | ||||||
| @ -5,8 +9,6 @@ use log::error; | |||||||
| use std::io::Write; | use std::io::Write; | ||||||
| use tokio::sync::mpsc::UnboundedSender; | use tokio::sync::mpsc::UnboundedSender; | ||||||
| 
 | 
 | ||||||
| use crate::player::stream::Stream; |  | ||||||
| 
 |  | ||||||
| pub enum SinkEvent { | pub enum SinkEvent { | ||||||
|   Start, |   Start, | ||||||
|   Stop, |   Stop, | ||||||
| @ -26,6 +28,8 @@ impl StreamSink { | |||||||
| impl Sink for StreamSink { | impl Sink for StreamSink { | ||||||
|   fn start(&mut self) -> SinkResult<()> { |   fn start(&mut self) -> SinkResult<()> { | ||||||
|     if let Err(why) = self.sender.send(SinkEvent::Start) { |     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}"); |       error!("Failed to send start playback event: {why}"); | ||||||
|       return Err(SinkError::ConnectionRefused(why.to_string())); |       return Err(SinkError::ConnectionRefused(why.to_string())); | ||||||
|     } |     } | ||||||
| @ -35,10 +39,14 @@ impl Sink for StreamSink { | |||||||
| 
 | 
 | ||||||
|   fn stop(&mut self) -> SinkResult<()> { |   fn stop(&mut self) -> SinkResult<()> { | ||||||
|     if let Err(why) = self.sender.send(SinkEvent::Stop) { |     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}"); |       error!("Failed to send start playback event: {why}"); | ||||||
|       return Err(SinkError::ConnectionRefused(why.to_string())); |       return Err(SinkError::ConnectionRefused(why.to_string())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     self.stream.flush().ok(); | ||||||
|  | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,8 +5,10 @@ use std::{ | |||||||
| 
 | 
 | ||||||
| use songbird::input::reader::MediaSource; | use songbird::input::reader::MediaSource; | ||||||
| 
 | 
 | ||||||
| // TODO: Find optimal value
 | /// The lower the value, the less latency
 | ||||||
| const MAX_SIZE: usize = 1024 * 1024; | ///
 | ||||||
|  | /// Too low of a value results in unpredictable audio
 | ||||||
|  | const MAX_SIZE: usize = 32 * 1024; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct Stream { | pub struct Stream { | ||||||
| @ -26,15 +28,19 @@ impl Read for Stream { | |||||||
|     let (mutex, condvar) = &*self.inner; |     let (mutex, condvar) = &*self.inner; | ||||||
|     let mut buffer = mutex.lock().expect("Mutex was poisoned"); |     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() { |       return Ok(buf.len()); | ||||||
|       buffer = condvar.wait(buffer).expect("Mutex was poisoned"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let max_read = usize::min(buf.len(), buffer.len()); |     let max_read = usize::min(buf.len(), buffer.len()); | ||||||
|     buf[0..max_read].copy_from_slice(&buffer[0..max_read]); |     buf[0..max_read].copy_from_slice(&buffer[0..max_read]); | ||||||
|     buffer.drain(0..max_read); |     buffer.drain(0..max_read); | ||||||
|  |     condvar.notify_all(); | ||||||
| 
 | 
 | ||||||
|     Ok(max_read) |     Ok(max_read) | ||||||
|   } |   } | ||||||
| @ -56,6 +62,12 @@ impl Write for Stream { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   fn flush(&mut self) -> std::io::Result<()> { |   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(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -25,7 +25,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO | |||||||
|                   author |                   author | ||||||
|                     .name("Maintained by: RoDaBaFilms") |                     .name("Maintained by: RoDaBaFilms") | ||||||
|                     .url("https://rodabafilms.com/") |                     .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)) |                 .description(format!("Current version: {}\n\nSpoticord is open source, check out [our GitHub](https://github.com/SpoticordMusic)", VERSION)) | ||||||
|                 .color(Status::Info as u64) |                 .color(Status::Info as u64) | ||||||
|  | |||||||
| @ -338,7 +338,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO | |||||||
|         .title("Connected to voice channel") |         .title("Connected to voice channel") | ||||||
|         .icon_url("https://spoticord.com/speaker.png") |         .icon_url("https://spoticord.com/speaker.png") | ||||||
|         .description(format!("Come listen along in <#{}>", channel_id)) |         .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) |         .status(Status::Info) | ||||||
|         .build(), |         .build(), | ||||||
|     ) |     ) | ||||||
|  | |||||||
| @ -91,6 +91,7 @@ async fn main() { | |||||||
|         _ = tokio::signal::ctrl_c() => { |         _ = tokio::signal::ctrl_c() => { | ||||||
|           info!("Received interrupt signal, shutting down..."); |           info!("Received interrupt signal, shutting down..."); | ||||||
| 
 | 
 | ||||||
|  |           session_manager.shutdown().await; | ||||||
|           shard_manager.lock().await.shutdown_all().await; |           shard_manager.lock().await.shutdown_all().await; | ||||||
| 
 | 
 | ||||||
|           break; |           break; | ||||||
| @ -110,6 +111,7 @@ async fn main() { | |||||||
|         }, if term.is_some() => { |         }, if term.is_some() => { | ||||||
|           info!("Received terminate signal, shutting down..."); |           info!("Received terminate signal, shutting down..."); | ||||||
| 
 | 
 | ||||||
|  |           session_manager.shutdown().await; | ||||||
|           shard_manager.lock().await.shutdown_all().await; |           shard_manager.lock().await.shutdown_all().await; | ||||||
| 
 | 
 | ||||||
|           break; |           break; | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| pub mod stream; |  | ||||||
| 
 |  | ||||||
| use librespot::{ | use librespot::{ | ||||||
|   connect::spirc::Spirc, |   connect::spirc::Spirc, | ||||||
|   core::{config::ConnectConfig, session::Session}, |   core::{ | ||||||
|  |     config::{ConnectConfig, SessionConfig}, | ||||||
|  |     session::Session, | ||||||
|  |   }, | ||||||
|   discovery::Credentials, |   discovery::Credentials, | ||||||
|   playback::{ |   playback::{ | ||||||
|     config::{Bitrate, PlayerConfig, VolumeCtrl}, |     config::{Bitrate, PlayerConfig, VolumeCtrl}, | ||||||
| @ -13,13 +14,11 @@ use librespot::{ | |||||||
| use tokio::sync::mpsc::UnboundedReceiver; | use tokio::sync::mpsc::UnboundedReceiver; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|   audio::{SinkEvent, StreamSink}, |   audio::{stream::Stream, SinkEvent, StreamSink}, | ||||||
|   librespot_ext::discovery::CredentialsExt, |   librespot_ext::discovery::CredentialsExt, | ||||||
|   utils, |   utils, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use self::stream::Stream; |  | ||||||
| 
 |  | ||||||
| pub struct Player { | pub struct Player { | ||||||
|   stream: Stream, |   stream: Stream, | ||||||
|   session: Option<Session>, |   session: Option<Session>, | ||||||
| @ -59,7 +58,16 @@ impl Player { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Connect the session
 |     // 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()); |     self.session = Some(session.clone()); | ||||||
| 
 | 
 | ||||||
|     let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { |     let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { | ||||||
|  | |||||||
| @ -115,6 +115,10 @@ impl InnerSessionManager { | |||||||
| 
 | 
 | ||||||
|     count |     count | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   pub fn sessions(&self) -> Vec<SpoticordSession> { | ||||||
|  |     self.sessions.values().cloned().collect() | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl SessionManager { | impl SessionManager { | ||||||
| @ -183,4 +187,13 @@ impl SessionManager { | |||||||
|   pub async fn get_active_session_count(&self) -> usize { |   pub async fn get_active_session_count(&self) -> usize { | ||||||
|     self.0.read().await.get_active_session_count().await |     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, |   tracks::TrackHandle, | ||||||
|   Call, Event, EventContext, EventHandler, |   Call, Event, EventContext, EventHandler, | ||||||
| }; | }; | ||||||
| use std::{sync::Arc, time::Duration}; | use std::{ | ||||||
| use tokio::sync::Mutex; |   io::Write, | ||||||
|  |   ops::{Deref, DerefMut}, | ||||||
|  |   sync::Arc, | ||||||
|  |   time::Duration, | ||||||
|  | }; | ||||||
|  | use tokio::sync::{Mutex, RwLockReadGuard, RwLockWriteGuard}; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>); | pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>); | ||||||
| @ -55,6 +60,8 @@ struct InnerSpoticordSession { | |||||||
| 
 | 
 | ||||||
|   spirc: Option<Spirc>, |   spirc: Option<Spirc>, | ||||||
| 
 | 
 | ||||||
|  |   player: Option<Player>, | ||||||
|  | 
 | ||||||
|   /// Whether the session has been disconnected
 |   /// Whether the session has been disconnected
 | ||||||
|   /// If this is true then this instance should no longer be used and dropped
 |   /// If this is true then this instance should no longer be used and dropped
 | ||||||
|   disconnected: bool, |   disconnected: bool, | ||||||
| @ -97,6 +104,7 @@ impl SpoticordSession { | |||||||
|       playback_info: None, |       playback_info: None, | ||||||
|       disconnect_handle: None, |       disconnect_handle: None, | ||||||
|       spirc: None, |       spirc: None, | ||||||
|  |       player: None, | ||||||
|       disconnected: false, |       disconnected: false, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
| @ -132,14 +140,13 @@ impl SpoticordSession { | |||||||
|       .clone(); |       .clone(); | ||||||
| 
 | 
 | ||||||
|     { |     { | ||||||
|       let mut inner = self.0.write().await; |       let mut inner = self.acquire_write().await; | ||||||
|       inner.owner = Some(owner_id); |       inner.owner = Some(owner_id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     { |     { | ||||||
|       let inner = self.0.clone(); |       let guild_id = self.acquire_read().await.guild_id; | ||||||
|       let inner = inner.read().await; |       session_manager.set_owner(owner_id, guild_id).await; | ||||||
|       session_manager.set_owner(owner_id, inner.guild_id).await; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Create the player
 |     // Create the player
 | ||||||
| @ -150,28 +157,28 @@ impl SpoticordSession { | |||||||
| 
 | 
 | ||||||
|   /// Advance to the next track
 |   /// Advance to the next track
 | ||||||
|   pub async fn next(&mut self) { |   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(); |       spirc.next(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Rewind to the previous track
 |   /// Rewind to the previous track
 | ||||||
|   pub async fn previous(&mut self) { |   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(); |       spirc.prev(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Pause the current track
 |   /// Pause the current track
 | ||||||
|   pub async fn pause(&mut self) { |   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(); |       spirc.pause(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Resume the current track
 |   /// Resume the current track
 | ||||||
|   pub async fn resume(&mut self) { |   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(); |       spirc.play(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -237,8 +244,7 @@ impl SpoticordSession { | |||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // Handle IPC packets
 |     // Handle events
 | ||||||
|     // This will automatically quit once the IPC connection is closed
 |  | ||||||
|     tokio::spawn({ |     tokio::spawn({ | ||||||
|       let track = track_handle.clone(); |       let track = track_handle.clone(); | ||||||
|       let ctx = ctx.clone(); |       let ctx = ctx.clone(); | ||||||
| @ -367,126 +373,16 @@ impl SpoticordSession { | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 SinkEvent::Stop => { |                 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
 |         // Clean up session
 | ||||||
| @ -497,9 +393,10 @@ impl SpoticordSession { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Update inner client and track
 |     // 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.track = Some(track_handle); | ||||||
|     inner.spirc = Some(spirc); |     inner.spirc = Some(spirc); | ||||||
|  |     inner.player = Some(player); | ||||||
| 
 | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   } |   } | ||||||
| @ -566,7 +463,7 @@ impl SpoticordSession { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Update track/episode
 |     // 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() { |     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); | ||||||
| @ -577,7 +474,7 @@ 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(&self) { |   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() { |     if let Some(spirc) = inner.spirc.take() { | ||||||
|       spirc.shutdown(); |       spirc.shutdown(); | ||||||
| @ -617,7 +514,7 @@ impl SpoticordSession { | |||||||
|     //  read lock to read the current owner.
 |     //  read lock to read the current owner.
 | ||||||
|     // This would deadlock if we have an active write lock
 |     // 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; |       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 { |       if is_none { | ||||||
|         inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); |         inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); | ||||||
| @ -659,8 +556,8 @@ impl SpoticordSession { | |||||||
|   async fn start_disconnect_timer(&self) { |   async fn start_disconnect_timer(&self) { | ||||||
|     self.stop_disconnect_timer().await; |     self.stop_disconnect_timer().await; | ||||||
| 
 | 
 | ||||||
|     let inner_arc = self.0.clone(); |     let arc_handle = self.0.clone(); | ||||||
|     let mut inner = inner_arc.write().await; |     let mut inner = self.acquire_write().await; | ||||||
| 
 | 
 | ||||||
|     // Check if we are already disconnected
 |     // Check if we are already disconnected
 | ||||||
|     if inner.disconnected { |     if inner.disconnected { | ||||||
| @ -668,7 +565,7 @@ impl SpoticordSession { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     inner.disconnect_handle = Some(tokio::spawn({ |     inner.disconnect_handle = Some(tokio::spawn({ | ||||||
|       let inner = inner_arc.clone(); |       let inner = arc_handle.clone(); | ||||||
|       let instance = self.clone(); |       let instance = self.clone(); | ||||||
| 
 | 
 | ||||||
|       async move { |       async move { | ||||||
| @ -705,7 +602,7 @@ impl SpoticordSession { | |||||||
| 
 | 
 | ||||||
|   /// Stop the disconnect timer (if one is running)
 |   /// Stop the disconnect timer (if one is running)
 | ||||||
|   async fn stop_disconnect_timer(&self) { |   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() { |     if let Some(handle) = inner.disconnect_handle.take() { | ||||||
|       handle.abort(); |       handle.abort(); | ||||||
|     } |     } | ||||||
| @ -714,7 +611,7 @@ impl SpoticordSession { | |||||||
|   /// Disconnect from the VC and send a message to the text channel
 |   /// Disconnect from the VC and send a message to the text channel
 | ||||||
|   pub async fn disconnect_with_message(&self, content: &str) { |   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
 |       // Firstly we disconnect
 | ||||||
|       inner.disconnect_no_abort().await; |       inner.disconnect_no_abort().await; | ||||||
| @ -745,48 +642,97 @@ impl SpoticordSession { | |||||||
| 
 | 
 | ||||||
|   /// Get the owner
 |   /// Get the owner
 | ||||||
|   pub async fn owner(&self) -> Option<UserId> { |   pub async fn owner(&self) -> Option<UserId> { | ||||||
|     self.0.read().await.owner |     self.acquire_read().await.owner | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Get the session manager
 |   /// Get the session manager
 | ||||||
|   pub async fn session_manager(&self) -> SessionManager { |   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
 |   /// Get the guild id
 | ||||||
|   pub async fn guild_id(&self) -> GuildId { |   pub async fn guild_id(&self) -> GuildId { | ||||||
|     self.0.read().await.guild_id |     self.acquire_read().await.guild_id | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Get the channel id
 |   /// Get the channel id
 | ||||||
|   pub async fn channel_id(&self) -> ChannelId { |   pub async fn channel_id(&self) -> ChannelId { | ||||||
|     self.0.read().await.channel_id |     self.acquire_read().await.channel_id | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Get the channel id
 |   /// Get the channel id
 | ||||||
|   #[allow(dead_code)] |   #[allow(dead_code)] | ||||||
|   pub async fn text_channel_id(&self) -> ChannelId { |   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
 |   /// Get the playback info
 | ||||||
|   pub async fn playback_info(&self) -> Option<PlaybackInfo> { |   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>> { |   pub async fn call(&self) -> Arc<Mutex<Call>> { | ||||||
|     self.0.read().await.call.clone() |     self.acquire_read().await.call.clone() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   #[allow(dead_code)] |   #[allow(dead_code)] | ||||||
|   pub async fn http(&self) -> Arc<Http> { |   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 { | impl InnerSpoticordSession { | ||||||
|   /// Internal version of disconnect, which does not abort the disconnect timer
 |   /// Internal version of disconnect, which does not abort the disconnect timer
 | ||||||
|   async fn disconnect_no_abort(&mut self) { |   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.disconnected = true; | ||||||
|     self |     self | ||||||
|       .session_manager |       .session_manager | ||||||
| @ -813,12 +759,6 @@ impl InnerSpoticordSession { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Drop for InnerSpoticordSession { |  | ||||||
|   fn drop(&mut self) { |  | ||||||
|     trace!("Dropping inner session"); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl EventHandler for SpoticordSession { | impl EventHandler for SpoticordSession { | ||||||
|   async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> { |   async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaXcess
						DaXcess