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