1 use librespot_core::authentication::Credentials; 2 use librespot_core::cache::Cache; 3 use librespot_core::config::SessionConfig; 4 use librespot_core::session::Session; 5 use librespot_core::session::SessionError; 6 use librespot_playback::config::PlayerConfig; 7 use librespot_playback::mixer::softmixer::SoftMixer; 8 use librespot_playback::mixer::MixerConfig; 9 use log::{debug, error, info}; 10 11 use librespot_playback::audio_backend; 12 use librespot_playback::config::Bitrate; 13 use librespot_playback::player::Player; 14 15 use futures::channel::oneshot; 16 use tokio::sync::mpsc; 17 18 use url::Url; 19 20 use std::env; 21 use std::str::FromStr; 22 use std::sync::{Arc, RwLock}; 23 use std::time::{Duration, SystemTime}; 24 25 use crate::config; 26 use crate::events::{Event, EventManager}; 27 use crate::model::playable::Playable; 28 use crate::spotify_api::WebApi; 29 use crate::spotify_worker::{Worker, WorkerCommand}; 30 31 pub const VOLUME_PERCENT: u16 = ((u16::max_value() as f64) * 1.0 / 100.0) as u16; 32 33 #[derive(Clone, Debug, PartialEq)] 34 pub enum PlayerEvent { 35 Playing(SystemTime), 36 Paused(Duration), 37 Stopped, 38 FinishedTrack, 39 } 40 41 #[derive(Clone)] 42 pub struct Spotify { 43 events: EventManager, 44 credentials: Credentials, 45 cfg: Arc<config::Config>, 46 status: Arc<RwLock<PlayerEvent>>, 47 pub api: WebApi, 48 elapsed: Arc<RwLock<Option<Duration>>>, 49 since: Arc<RwLock<Option<SystemTime>>>, 50 token_issued: Arc<RwLock<Option<SystemTime>>>, 51 channel: Arc<RwLock<Option<mpsc::UnboundedSender<WorkerCommand>>>>, 52 user: Option<String>, 53 } 54 55 impl Spotify { new( events: EventManager, credentials: Credentials, cfg: Arc<config::Config>, ) -> Spotify56 pub fn new( 57 events: EventManager, 58 credentials: Credentials, 59 cfg: Arc<config::Config>, 60 ) -> Spotify { 61 let mut spotify = Spotify { 62 events, 63 credentials, 64 cfg: cfg.clone(), 65 status: Arc::new(RwLock::new(PlayerEvent::Stopped)), 66 api: WebApi::new(), 67 elapsed: Arc::new(RwLock::new(None)), 68 since: Arc::new(RwLock::new(None)), 69 token_issued: Arc::new(RwLock::new(None)), 70 channel: Arc::new(RwLock::new(None)), 71 user: None, 72 }; 73 74 let (user_tx, user_rx) = oneshot::channel(); 75 spotify.start_worker(Some(user_tx)); 76 spotify.user = futures::executor::block_on(user_rx).ok(); 77 let volume = cfg.state().volume; 78 spotify.set_volume(volume); 79 80 spotify.api.set_worker_channel(spotify.channel.clone()); 81 spotify.api.update_token(); 82 83 spotify.api.set_user(spotify.user.clone()); 84 85 spotify 86 } 87 start_worker(&self, user_tx: Option<oneshot::Sender<String>>)88 pub fn start_worker(&self, user_tx: Option<oneshot::Sender<String>>) { 89 let (tx, rx) = mpsc::unbounded_channel(); 90 *self 91 .channel 92 .write() 93 .expect("can't writelock worker channel") = Some(tx); 94 { 95 let worker_channel = self.channel.clone(); 96 let cfg = self.cfg.clone(); 97 let events = self.events.clone(); 98 let volume = self.volume(); 99 let credentials = self.credentials.clone(); 100 let handle = tokio::runtime::Handle::current(); 101 handle.spawn(async move { 102 Self::worker( 103 worker_channel, 104 events, 105 rx, 106 cfg.clone(), 107 credentials, 108 user_tx, 109 volume, 110 ) 111 .await 112 }); 113 } 114 } 115 session_config() -> SessionConfig116 pub fn session_config() -> SessionConfig { 117 let mut session_config = SessionConfig::default(); 118 match env::var("http_proxy") { 119 Ok(proxy) => { 120 info!("Setting HTTP proxy {}", proxy); 121 session_config.proxy = Url::parse(&proxy).ok(); 122 } 123 Err(_) => debug!("No HTTP proxy set"), 124 } 125 session_config 126 } 127 test_credentials(credentials: Credentials) -> Result<Session, SessionError>128 pub fn test_credentials(credentials: Credentials) -> Result<Session, SessionError> { 129 let config = Self::session_config(); 130 let handle = tokio::runtime::Handle::current(); 131 let jh = handle.spawn(async { Session::connect(config, credentials, None).await }); 132 futures::executor::block_on(jh).unwrap() 133 } 134 create_session( cfg: &config::Config, credentials: Credentials, ) -> Result<Session, SessionError>135 async fn create_session( 136 cfg: &config::Config, 137 credentials: Credentials, 138 ) -> Result<Session, SessionError> { 139 let session_config = Self::session_config(); 140 let audio_cache_path = match cfg.values().audio_cache.unwrap_or(true) { 141 true => Some(config::cache_path("librespot").join("files")), 142 false => None, 143 }; 144 let cache = Cache::new( 145 Some(config::cache_path("librespot")), 146 audio_cache_path, 147 cfg.values() 148 .audio_cache_size 149 .map(|size| (size * 1048576) as u64), 150 ) 151 .expect("Could not create cache"); 152 debug!("opening spotify session"); 153 Session::connect(session_config, credentials, Some(cache)).await 154 } 155 worker( worker_channel: Arc<RwLock<Option<mpsc::UnboundedSender<WorkerCommand>>>>, events: EventManager, commands: mpsc::UnboundedReceiver<WorkerCommand>, cfg: Arc<config::Config>, credentials: Credentials, user_tx: Option<oneshot::Sender<String>>, volume: u16, )156 async fn worker( 157 worker_channel: Arc<RwLock<Option<mpsc::UnboundedSender<WorkerCommand>>>>, 158 events: EventManager, 159 commands: mpsc::UnboundedReceiver<WorkerCommand>, 160 cfg: Arc<config::Config>, 161 credentials: Credentials, 162 user_tx: Option<oneshot::Sender<String>>, 163 volume: u16, 164 ) { 165 let bitrate_str = cfg.values().bitrate.unwrap_or(320).to_string(); 166 let bitrate = Bitrate::from_str(&bitrate_str); 167 if bitrate.is_err() { 168 error!("invalid bitrate, will use 320 instead") 169 } 170 171 let player_config = PlayerConfig { 172 gapless: cfg.values().gapless.unwrap_or(false), 173 bitrate: bitrate.unwrap_or(Bitrate::Bitrate320), 174 normalisation: cfg.values().volnorm.unwrap_or(false), 175 normalisation_pregain: cfg.values().volnorm_pregain.unwrap_or(0.0), 176 ..Default::default() 177 }; 178 179 let session = Self::create_session(&cfg, credentials) 180 .await 181 .expect("Could not create session"); 182 user_tx.map(|tx| tx.send(session.username())); 183 184 let create_mixer = librespot_playback::mixer::find(Some(SoftMixer::NAME)) 185 .expect("could not create softvol mixer"); 186 let mixer = create_mixer(MixerConfig::default()); 187 mixer.set_volume(volume); 188 189 let backend = audio_backend::find(cfg.values().backend.clone()).unwrap(); 190 let audio_format: librespot_playback::config::AudioFormat = Default::default(); 191 let (player, player_events) = Player::new( 192 player_config, 193 session.clone(), 194 mixer.get_audio_filter(), 195 move || (backend)(cfg.values().backend_device.clone(), audio_format), 196 ); 197 198 let mut worker = Worker::new( 199 events.clone(), 200 player_events, 201 commands, 202 session, 203 player, 204 mixer, 205 ); 206 debug!("worker thread ready."); 207 worker.run_loop().await; 208 209 error!("worker thread died, requesting restart"); 210 *worker_channel 211 .write() 212 .expect("can't writelock worker channel") = None; 213 events.send(Event::SessionDied) 214 } 215 get_current_status(&self) -> PlayerEvent216 pub fn get_current_status(&self) -> PlayerEvent { 217 let status = self 218 .status 219 .read() 220 .expect("could not acquire read lock on playback status"); 221 (*status).clone() 222 } 223 get_current_progress(&self) -> Duration224 pub fn get_current_progress(&self) -> Duration { 225 self.get_elapsed().unwrap_or_else(|| Duration::from_secs(0)) 226 + self 227 .get_since() 228 .map(|t| t.elapsed().unwrap()) 229 .unwrap_or_else(|| Duration::from_secs(0)) 230 } 231 set_elapsed(&self, new_elapsed: Option<Duration>)232 fn set_elapsed(&self, new_elapsed: Option<Duration>) { 233 let mut elapsed = self 234 .elapsed 235 .write() 236 .expect("could not acquire write lock on elapsed time"); 237 *elapsed = new_elapsed; 238 } 239 get_elapsed(&self) -> Option<Duration>240 fn get_elapsed(&self) -> Option<Duration> { 241 let elapsed = self 242 .elapsed 243 .read() 244 .expect("could not acquire read lock on elapsed time"); 245 *elapsed 246 } 247 set_since(&self, new_since: Option<SystemTime>)248 fn set_since(&self, new_since: Option<SystemTime>) { 249 let mut since = self 250 .since 251 .write() 252 .expect("could not acquire write lock on since time"); 253 *since = new_since; 254 } 255 get_since(&self) -> Option<SystemTime>256 fn get_since(&self) -> Option<SystemTime> { 257 let since = self 258 .since 259 .read() 260 .expect("could not acquire read lock on since time"); 261 *since 262 } 263 load(&self, track: &Playable, start_playing: bool, position_ms: u32)264 pub fn load(&self, track: &Playable, start_playing: bool, position_ms: u32) { 265 info!("loading track: {:?}", track); 266 self.send_worker(WorkerCommand::Load( 267 track.clone(), 268 start_playing, 269 position_ms, 270 )); 271 } 272 update_status(&self, new_status: PlayerEvent)273 pub fn update_status(&self, new_status: PlayerEvent) { 274 match new_status { 275 PlayerEvent::Paused(position) => { 276 self.set_elapsed(Some(position)); 277 self.set_since(None); 278 } 279 PlayerEvent::Playing(playback_start) => { 280 self.set_since(Some(playback_start)); 281 self.set_elapsed(None); 282 } 283 PlayerEvent::Stopped | PlayerEvent::FinishedTrack => { 284 self.set_elapsed(None); 285 self.set_since(None); 286 } 287 } 288 289 let mut status = self 290 .status 291 .write() 292 .expect("could not acquire write lock on player status"); 293 *status = new_status; 294 } 295 update_track(&self)296 pub fn update_track(&self) { 297 self.set_elapsed(None); 298 self.set_since(None); 299 } 300 play(&self)301 pub fn play(&self) { 302 info!("play()"); 303 self.send_worker(WorkerCommand::Play); 304 } 305 toggleplayback(&self)306 pub fn toggleplayback(&self) { 307 match self.get_current_status() { 308 PlayerEvent::Playing(_) => self.pause(), 309 PlayerEvent::Paused(_) => self.play(), 310 _ => (), 311 } 312 } 313 send_worker(&self, cmd: WorkerCommand)314 fn send_worker(&self, cmd: WorkerCommand) { 315 let channel = self.channel.read().expect("can't readlock worker channel"); 316 match channel.as_ref() { 317 Some(channel) => channel.send(cmd).expect("can't send message to worker"), 318 None => error!("no channel to worker available"), 319 } 320 } 321 pause(&self)322 pub fn pause(&self) { 323 info!("pause()"); 324 self.send_worker(WorkerCommand::Pause); 325 } 326 stop(&self)327 pub fn stop(&self) { 328 info!("stop()"); 329 self.send_worker(WorkerCommand::Stop); 330 } 331 seek(&self, position_ms: u32)332 pub fn seek(&self, position_ms: u32) { 333 self.send_worker(WorkerCommand::Seek(position_ms)); 334 } 335 seek_relative(&self, delta: i32)336 pub fn seek_relative(&self, delta: i32) { 337 let progress = self.get_current_progress(); 338 let new = (progress.as_secs() * 1000) as i32 + progress.subsec_millis() as i32 + delta; 339 self.seek(std::cmp::max(0, new) as u32); 340 } 341 volume(&self) -> u16342 pub fn volume(&self) -> u16 { 343 self.cfg.state().volume 344 } 345 set_volume(&self, volume: u16)346 pub fn set_volume(&self, volume: u16) { 347 info!("setting volume to {}", volume); 348 self.cfg.with_state_mut(|mut s| s.volume = volume); 349 self.send_worker(WorkerCommand::SetVolume(volume)); 350 } 351 preload(&self, track: &Playable)352 pub fn preload(&self, track: &Playable) { 353 self.send_worker(WorkerCommand::Preload(track.clone())); 354 } 355 shutdown(&self)356 pub fn shutdown(&self) { 357 self.send_worker(WorkerCommand::Shutdown); 358 } 359 } 360 361 #[derive(Debug, PartialEq)] 362 pub enum UriType { 363 Album, 364 Artist, 365 Track, 366 Playlist, 367 Show, 368 Episode, 369 } 370 371 impl UriType { from_uri(s: &str) -> Option<UriType>372 pub fn from_uri(s: &str) -> Option<UriType> { 373 if s.starts_with("spotify:album:") { 374 Some(UriType::Album) 375 } else if s.starts_with("spotify:artist:") { 376 Some(UriType::Artist) 377 } else if s.starts_with("spotify:track:") { 378 Some(UriType::Track) 379 } else if s.starts_with("spotify:") && s.contains(":playlist:") { 380 Some(UriType::Playlist) 381 } else if s.starts_with("spotify:show:") { 382 Some(UriType::Show) 383 } else if s.starts_with("spotify:episode:") { 384 Some(UriType::Episode) 385 } else { 386 None 387 } 388 } 389 } 390