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:
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user