From ace23462f7177ba8d14af354a881a03ff5936ff1 Mon Sep 17 00:00:00 2001 From: Henrik Friedrichsen Date: Wed, 26 Jul 2023 02:02:48 +0200 Subject: [PATCH] Migrate ncspot to librespot 0.5 breaking changes - Set `client_id` via `SessionConfig` - Use `TokenProvider` to obtain client token instead of custom Mercury call - Other minor changes --- src/authentication.rs | 4 ++-- src/spotify.rs | 25 ++++++++++++---------- src/spotify_api.rs | 6 +++--- src/spotify_worker.rs | 49 +++++++++++++++---------------------------- 4 files changed, 36 insertions(+), 48 deletions(-) diff --git a/src/authentication.rs b/src/authentication.rs index 4f86d3c0..ccbdb6e7 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -99,7 +99,7 @@ pub fn create_credentials() -> Result { .as_bytes() .to_vec(); s.set_user_data::>(Ok(RespotCredentials { - username, + username: Some(username), auth_type: AuthenticationType::AUTHENTICATION_USER_PASS, auth_data, })); @@ -146,7 +146,7 @@ pub fn credentials_eval( let password = eval(password_cmd)?; Ok(RespotCredentials { - username, + username: Some(username), auth_type: AuthenticationType::AUTHENTICATION_USER_PASS, auth_data: password, }) diff --git a/src/spotify.rs b/src/spotify.rs index 36d754e1..40360fd5 100644 --- a/src/spotify.rs +++ b/src/spotify.rs @@ -9,7 +9,6 @@ use librespot_core::authentication::Credentials; use librespot_core::cache::Cache; use librespot_core::config::SessionConfig; use librespot_core::session::Session; -use librespot_core::session::SessionError; use librespot_playback::audio_backend; use librespot_playback::audio_backend::SinkBuilder; use librespot_playback::config::Bitrate; @@ -129,7 +128,10 @@ impl Spotify { /// Generate the librespot [SessionConfig] used when creating a [Session]. pub fn session_config(cfg: &config::Config) -> SessionConfig { - let mut session_config = SessionConfig::default(); + let mut session_config = librespot_core::SessionConfig { + client_id: config::CLIENT_ID.to_string(), + ..Default::default() + }; match env::var("http_proxy") { Ok(proxy) => { info!("Setting HTTP proxy {}", proxy); @@ -143,17 +145,18 @@ impl Spotify { session_config } - /// Test whether `credentials` are valid Spotify credentials. pub fn test_credentials( cfg: &config::Config, credentials: Credentials, - ) -> Result { + ) -> Result { let config = Self::session_config(cfg); + let _guard = ASYNC_RUNTIME.get().unwrap().enter(); + let session = Session::new(config, None); ASYNC_RUNTIME .get() .unwrap() - .block_on(Session::connect(config, credentials, None, true)) - .map(|r| r.0) + .block_on(session.connect(credentials, true)) + .map(|_| session) } /// Create a [Session] that respects the user configuration in `cfg` and with the given @@ -161,7 +164,7 @@ impl Spotify { async fn create_session( cfg: &config::Config, credentials: Credentials, - ) -> Result { + ) -> Result { let librespot_cache_path = config::cache_path("librespot"); let audio_cache_path = if let Some(false) = cfg.values().audio_cache { None @@ -179,9 +182,8 @@ impl Spotify { .expect("Could not create cache"); debug!("opening spotify session"); let session_config = Self::session_config(cfg); - Session::connect(session_config, credentials, Some(cache), true) - .await - .map(|r| r.0) + let session = Session::new(session_config, Some(cache)); + session.connect(credentials, true).await.map(|_| session) } /// Create and initialize the requested audio backend. @@ -248,12 +250,13 @@ impl Spotify { mixer.set_volume(volume); let audio_format: librespot_playback::config::AudioFormat = Default::default(); - let (player, player_events) = Player::new( + let player = Player::new( player_config, session.clone(), mixer.get_soft_volume(), move || (backend)(cfg.values().backend_device.clone(), audio_format), ); + let player_events = player.get_player_event_channel(); let mut worker = Worker::new( events.clone(), diff --git a/src/spotify_api.rs b/src/spotify_api.rs index 6b626c1a..3b233736 100644 --- a/src/spotify_api.rs +++ b/src/spotify_api.rs @@ -105,13 +105,13 @@ impl WebApi { if let Ok(Some(token)) = token_rx.recv() { *api_token.lock().unwrap() = Some(Token { access_token: token.access_token, - expires_in: chrono::Duration::try_seconds(token.expires_in.into()).unwrap(), - scopes: HashSet::from_iter(token.scope), + expires_in: chrono::Duration::from_std(token.expires_in).unwrap(), + scopes: HashSet::from_iter(token.scopes), expires_at: None, refresh_token: None, }); *api_token_expiration.write().unwrap() = - Utc::now() + ChronoDuration::try_seconds(token.expires_in.into()).unwrap(); + Utc::now() + ChronoDuration::from_std(token.expires_in).unwrap(); } else { error!("Failed to update token"); } diff --git a/src/spotify_worker.rs b/src/spotify_worker.rs index 3073aad0..8caab167 100644 --- a/src/spotify_worker.rs +++ b/src/spotify_worker.rs @@ -1,16 +1,17 @@ -use crate::config; use crate::events::{Event, EventManager}; use crate::model::playable::Playable; use crate::queue::QueueEvent; use crate::spotify::PlayerEvent; -use futures::{Future, FutureExt}; -use librespot_core::keymaster::Token; +use futures::Future; +use futures::FutureExt; use librespot_core::session::Session; -use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId}; +use librespot_core::spotify_id::SpotifyId; +use librespot_core::token::Token; use librespot_playback::mixer::Mixer; use librespot_playback::player::{Player, PlayerEvent as LibrespotPlayerEvent}; use log::{debug, error, info, warn}; use std::sync::mpsc::Sender; +use std::sync::Arc; use std::time::Duration; use std::{pin::Pin, time::SystemTime}; use tokio::sync::mpsc; @@ -36,10 +37,10 @@ pub struct Worker { player_events: UnboundedReceiverStream, commands: UnboundedReceiverStream, session: Session, - player: Player, + player: Arc, token_task: Pin + Send>>, active: bool, - mixer: Box, + mixer: Arc, } impl Worker { @@ -48,8 +49,8 @@ impl Worker { player_events: mpsc::UnboundedReceiver, commands: mpsc::UnboundedReceiver, session: Session, - player: Player, - mixer: Box, + player: Arc, + mixer: Arc, ) -> Self { Self { events, @@ -63,27 +64,13 @@ impl Worker { } } - fn get_token(&self, sender: Sender>) -> Pin + Send>> { - let client_id = config::CLIENT_ID; + async fn get_token(session: Session, sender: Sender>) { let scopes = "user-read-private,playlist-read-private,playlist-read-collaborative,playlist-modify-public,playlist-modify-private,user-follow-modify,user-follow-read,user-library-read,user-library-modify,user-top-read,user-read-recently-played"; - let url = - format!("hm://keymaster/token/authenticated?client_id={client_id}&scope={scopes}"); - Box::pin( - self.session - .mercury() - .get(url) - .map(move |response| { - response.ok().and_then(move |response| { - let payload = response.payload.first()?; - - let data = String::from_utf8(payload.clone()).ok()?; - let token: Token = serde_json::from_str(&data).ok()?; - info!("new token received: {:?}", token); - Some(token) - }) - }) - .map(move |result| sender.send(result).unwrap()), - ) + session + .token_provider() + .get_token(scopes) + .map(|response| sender.send(response.ok()).expect("token channel is closed")) + .await; } pub async fn run_loop(&mut self) { @@ -102,7 +89,7 @@ impl Worker { match SpotifyId::from_uri(&playable.uri()) { Ok(id) => { info!("player loading track: {:?}", id); - if id.audio_type == SpotifyAudioType::NonPlayable { + if !id.is_playable() { warn!("track is not playable"); self.events.send(Event::Player(PlayerEvent::FinishedTrack)); } else { @@ -131,7 +118,7 @@ impl Worker { self.mixer.set_volume(volume); } Some(WorkerCommand::RequestToken(sender)) => { - self.token_task = self.get_token(sender); + self.token_task = Box::pin(Self::get_token(self.session.clone(), sender)); } Some(WorkerCommand::Preload(playable)) => { if let Ok(id) = SpotifyId::from_uri(&playable.uri()) { @@ -150,7 +137,6 @@ impl Worker { play_request_id: _, track_id: _, position_ms, - duration_ms: _, }) => { let position = Duration::from_millis(position_ms as u64); let playback_start = SystemTime::now() - position; @@ -162,7 +148,6 @@ impl Worker { play_request_id: _, track_id: _, position_ms, - duration_ms: _, }) => { let position = Duration::from_millis(position_ms as u64); self.events