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
This commit is contained in:
Henrik Friedrichsen
2023-07-26 02:02:48 +02:00
parent 4bebdef81b
commit ace23462f7
4 changed files with 36 additions and 48 deletions

View File

@@ -99,7 +99,7 @@ pub fn create_credentials() -> Result<RespotCredentials, String> {
.as_bytes() .as_bytes()
.to_vec(); .to_vec();
s.set_user_data::<Result<RespotCredentials, String>>(Ok(RespotCredentials { s.set_user_data::<Result<RespotCredentials, String>>(Ok(RespotCredentials {
username, username: Some(username),
auth_type: AuthenticationType::AUTHENTICATION_USER_PASS, auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
auth_data, auth_data,
})); }));
@@ -146,7 +146,7 @@ pub fn credentials_eval(
let password = eval(password_cmd)?; let password = eval(password_cmd)?;
Ok(RespotCredentials { Ok(RespotCredentials {
username, username: Some(username),
auth_type: AuthenticationType::AUTHENTICATION_USER_PASS, auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
auth_data: password, auth_data: password,
}) })

View File

@@ -9,7 +9,6 @@ use librespot_core::authentication::Credentials;
use librespot_core::cache::Cache; use librespot_core::cache::Cache;
use librespot_core::config::SessionConfig; use librespot_core::config::SessionConfig;
use librespot_core::session::Session; use librespot_core::session::Session;
use librespot_core::session::SessionError;
use librespot_playback::audio_backend; use librespot_playback::audio_backend;
use librespot_playback::audio_backend::SinkBuilder; use librespot_playback::audio_backend::SinkBuilder;
use librespot_playback::config::Bitrate; use librespot_playback::config::Bitrate;
@@ -129,7 +128,10 @@ impl Spotify {
/// Generate the librespot [SessionConfig] used when creating a [Session]. /// Generate the librespot [SessionConfig] used when creating a [Session].
pub fn session_config(cfg: &config::Config) -> SessionConfig { 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") { match env::var("http_proxy") {
Ok(proxy) => { Ok(proxy) => {
info!("Setting HTTP proxy {}", proxy); info!("Setting HTTP proxy {}", proxy);
@@ -143,17 +145,18 @@ impl Spotify {
session_config session_config
} }
/// Test whether `credentials` are valid Spotify credentials.
pub fn test_credentials( pub fn test_credentials(
cfg: &config::Config, cfg: &config::Config,
credentials: Credentials, credentials: Credentials,
) -> Result<Session, SessionError> { ) -> Result<Session, librespot_core::Error> {
let config = Self::session_config(cfg); let config = Self::session_config(cfg);
let _guard = ASYNC_RUNTIME.get().unwrap().enter();
let session = Session::new(config, None);
ASYNC_RUNTIME ASYNC_RUNTIME
.get() .get()
.unwrap() .unwrap()
.block_on(Session::connect(config, credentials, None, true)) .block_on(session.connect(credentials, true))
.map(|r| r.0) .map(|_| session)
} }
/// Create a [Session] that respects the user configuration in `cfg` and with the given /// Create a [Session] that respects the user configuration in `cfg` and with the given
@@ -161,7 +164,7 @@ impl Spotify {
async fn create_session( async fn create_session(
cfg: &config::Config, cfg: &config::Config,
credentials: Credentials, credentials: Credentials,
) -> Result<Session, SessionError> { ) -> Result<Session, librespot_core::Error> {
let librespot_cache_path = config::cache_path("librespot"); let librespot_cache_path = config::cache_path("librespot");
let audio_cache_path = if let Some(false) = cfg.values().audio_cache { let audio_cache_path = if let Some(false) = cfg.values().audio_cache {
None None
@@ -179,9 +182,8 @@ impl Spotify {
.expect("Could not create cache"); .expect("Could not create cache");
debug!("opening spotify session"); debug!("opening spotify session");
let session_config = Self::session_config(cfg); let session_config = Self::session_config(cfg);
Session::connect(session_config, credentials, Some(cache), true) let session = Session::new(session_config, Some(cache));
.await session.connect(credentials, true).await.map(|_| session)
.map(|r| r.0)
} }
/// Create and initialize the requested audio backend. /// Create and initialize the requested audio backend.
@@ -248,12 +250,13 @@ impl Spotify {
mixer.set_volume(volume); mixer.set_volume(volume);
let audio_format: librespot_playback::config::AudioFormat = Default::default(); let audio_format: librespot_playback::config::AudioFormat = Default::default();
let (player, player_events) = Player::new( let player = Player::new(
player_config, player_config,
session.clone(), session.clone(),
mixer.get_soft_volume(), mixer.get_soft_volume(),
move || (backend)(cfg.values().backend_device.clone(), audio_format), move || (backend)(cfg.values().backend_device.clone(), audio_format),
); );
let player_events = player.get_player_event_channel();
let mut worker = Worker::new( let mut worker = Worker::new(
events.clone(), events.clone(),

View File

@@ -105,13 +105,13 @@ impl WebApi {
if let Ok(Some(token)) = token_rx.recv() { if let Ok(Some(token)) = token_rx.recv() {
*api_token.lock().unwrap() = Some(Token { *api_token.lock().unwrap() = Some(Token {
access_token: token.access_token, access_token: token.access_token,
expires_in: chrono::Duration::try_seconds(token.expires_in.into()).unwrap(), expires_in: chrono::Duration::from_std(token.expires_in).unwrap(),
scopes: HashSet::from_iter(token.scope), scopes: HashSet::from_iter(token.scopes),
expires_at: None, expires_at: None,
refresh_token: None, refresh_token: None,
}); });
*api_token_expiration.write().unwrap() = *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 { } else {
error!("Failed to update token"); error!("Failed to update token");
} }

View File

@@ -1,16 +1,17 @@
use crate::config;
use crate::events::{Event, EventManager}; use crate::events::{Event, EventManager};
use crate::model::playable::Playable; use crate::model::playable::Playable;
use crate::queue::QueueEvent; use crate::queue::QueueEvent;
use crate::spotify::PlayerEvent; use crate::spotify::PlayerEvent;
use futures::{Future, FutureExt}; use futures::Future;
use librespot_core::keymaster::Token; use futures::FutureExt;
use librespot_core::session::Session; 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::mixer::Mixer;
use librespot_playback::player::{Player, PlayerEvent as LibrespotPlayerEvent}; use librespot_playback::player::{Player, PlayerEvent as LibrespotPlayerEvent};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::{pin::Pin, time::SystemTime}; use std::{pin::Pin, time::SystemTime};
use tokio::sync::mpsc; use tokio::sync::mpsc;
@@ -36,10 +37,10 @@ pub struct Worker {
player_events: UnboundedReceiverStream<LibrespotPlayerEvent>, player_events: UnboundedReceiverStream<LibrespotPlayerEvent>,
commands: UnboundedReceiverStream<WorkerCommand>, commands: UnboundedReceiverStream<WorkerCommand>,
session: Session, session: Session,
player: Player, player: Arc<Player>,
token_task: Pin<Box<dyn Future<Output = ()> + Send>>, token_task: Pin<Box<dyn Future<Output = ()> + Send>>,
active: bool, active: bool,
mixer: Box<dyn Mixer>, mixer: Arc<dyn Mixer>,
} }
impl Worker { impl Worker {
@@ -48,8 +49,8 @@ impl Worker {
player_events: mpsc::UnboundedReceiver<LibrespotPlayerEvent>, player_events: mpsc::UnboundedReceiver<LibrespotPlayerEvent>,
commands: mpsc::UnboundedReceiver<WorkerCommand>, commands: mpsc::UnboundedReceiver<WorkerCommand>,
session: Session, session: Session,
player: Player, player: Arc<Player>,
mixer: Box<dyn Mixer>, mixer: Arc<dyn Mixer>,
) -> Self { ) -> Self {
Self { Self {
events, events,
@@ -63,27 +64,13 @@ impl Worker {
} }
} }
fn get_token(&self, sender: Sender<Option<Token>>) -> Pin<Box<dyn Future<Output = ()> + Send>> { async fn get_token(session: Session, sender: Sender<Option<Token>>) {
let client_id = config::CLIENT_ID;
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 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 = session
format!("hm://keymaster/token/authenticated?client_id={client_id}&scope={scopes}"); .token_provider()
Box::pin( .get_token(scopes)
self.session .map(|response| sender.send(response.ok()).expect("token channel is closed"))
.mercury() .await;
.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()),
)
} }
pub async fn run_loop(&mut self) { pub async fn run_loop(&mut self) {
@@ -102,7 +89,7 @@ impl Worker {
match SpotifyId::from_uri(&playable.uri()) { match SpotifyId::from_uri(&playable.uri()) {
Ok(id) => { Ok(id) => {
info!("player loading track: {:?}", id); info!("player loading track: {:?}", id);
if id.audio_type == SpotifyAudioType::NonPlayable { if !id.is_playable() {
warn!("track is not playable"); warn!("track is not playable");
self.events.send(Event::Player(PlayerEvent::FinishedTrack)); self.events.send(Event::Player(PlayerEvent::FinishedTrack));
} else { } else {
@@ -131,7 +118,7 @@ impl Worker {
self.mixer.set_volume(volume); self.mixer.set_volume(volume);
} }
Some(WorkerCommand::RequestToken(sender)) => { 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)) => { Some(WorkerCommand::Preload(playable)) => {
if let Ok(id) = SpotifyId::from_uri(&playable.uri()) { if let Ok(id) = SpotifyId::from_uri(&playable.uri()) {
@@ -150,7 +137,6 @@ impl Worker {
play_request_id: _, play_request_id: _,
track_id: _, track_id: _,
position_ms, position_ms,
duration_ms: _,
}) => { }) => {
let position = Duration::from_millis(position_ms as u64); let position = Duration::from_millis(position_ms as u64);
let playback_start = SystemTime::now() - position; let playback_start = SystemTime::now() - position;
@@ -162,7 +148,6 @@ impl Worker {
play_request_id: _, play_request_id: _,
track_id: _, track_id: _,
position_ms, position_ms,
duration_ms: _,
}) => { }) => {
let position = Duration::from_millis(position_ms as u64); let position = Duration::from_millis(position_ms as u64);
self.events self.events