1
0
Fork 0
mirror of https://github.com/librespot-org/librespot.git synced 2025-10-03 09:49:31 +02:00

refactor: update to Rust 1.85 and edition 2024, use inline log args

- Update MSRV to 1.85 and Rust edition to 2024.
- Refactor all logging macros to use inline argument formatting.
- Fix import order in main.rs and examples.
- Add async environment variable setter to main.rs as safe facade.
This commit is contained in:
Roderick van Domburg 2025-08-13 16:19:39 +02:00
parent 0aec38b07a
commit 6288e7e03c
No known key found for this signature in database
GPG key ID: 607FA06CB5236AE0
30 changed files with 419 additions and 448 deletions

View file

@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- [core] MSRV is now 1.81 (breaking)
- [core] MSRV is now 1.85 with Rust edition 2024 (breaking)
- [core] AP connect and handshake have a combined 5 second timeout.
- [core] `stream_from_cdn` now accepts the URL as `TryInto<Uri>` instead of `CdnUrl` (breaking)
- [connect] Replaced `has_volume_ctrl` with `disable_volume` in `ConnectConfig` (breaking)

View file

@ -1,14 +1,14 @@
[package]
name = "librespot"
version = "0.6.0-dev"
rust-version = "1.81"
rust-version = "1.85"
authors = ["Librespot Org"]
license = "MIT"
description = "An open source client library for Spotify, with support for Spotify Connect"
keywords = ["spotify"]
repository = "https://github.com/librespot-org/librespot"
readme = "README.md"
edition = "2021"
edition = "2024"
[workspace]
@ -126,4 +126,4 @@ assets = [
]
[workspace.package]
rust-version = "1.81"
rust-version = "1.85"

View file

@ -366,11 +366,11 @@ impl AudioFile {
bytes_per_second: usize,
) -> Result<AudioFile, Error> {
if let Some(file) = session.cache().and_then(|cache| cache.file(file_id)) {
debug!("File {} already in cache", file_id);
debug!("File {file_id} already in cache");
return Ok(AudioFile::Cached(file));
}
debug!("Downloading file {}", file_id);
debug!("Downloading file {file_id}");
let (complete_tx, complete_rx) = oneshot::channel();
@ -379,14 +379,14 @@ impl AudioFile {
let session_ = session.clone();
session.spawn(complete_rx.map_ok(move |mut file| {
debug!("Downloading file {} complete", file_id);
debug!("Downloading file {file_id} complete");
if let Some(cache) = session_.cache() {
if let Some(cache_id) = cache.file_path(file_id) {
if let Err(e) = cache.save_file(file_id, &mut file) {
error!("Error caching file {} to {:?}: {}", file_id, cache_id, e);
error!("Error caching file {file_id} to {cache_id:?}: {e}");
} else {
debug!("File {} cached to {:?}", file_id, cache_id);
debug!("File {file_id} cached to {cache_id:?}");
}
}
}
@ -465,14 +465,11 @@ impl AudioFileStreaming {
)));
};
trace!("Streaming from {}", url);
trace!("Streaming from {url}");
let code = response.status();
if code != StatusCode::PARTIAL_CONTENT {
debug!(
"Opening audio file expected partial content but got: {}",
code
);
debug!("Opening audio file expected partial content but got: {code}");
return Err(AudioFileError::StatusCode(code).into());
}

View file

@ -166,7 +166,7 @@ impl Spirc {
}
let spirc_id = SPIRC_COUNTER.fetch_add(1, Ordering::AcqRel);
debug!("new Spirc[{}]", spirc_id);
debug!("new Spirc[{spirc_id}]");
let connect_state = ConnectState::new(config, &session);
@ -446,14 +446,14 @@ impl SpircTask {
cluster_update = self.connect_state_update.next() => unwrap! {
cluster_update,
match |cluster_update| if let Err(e) = self.handle_cluster_update(cluster_update).await {
error!("could not dispatch connect state update: {}", e);
error!("could not dispatch connect state update: {e}");
}
},
// main dealer request handling (dealer expects an answer)
request = self.connect_state_command.next() => unwrap! {
request,
|request| if let Err(e) = self.handle_connect_state_request(request).await {
error!("couldn't handle connect state command: {}", e);
error!("couldn't handle connect state command: {e}");
}
},
// volume request handling is send separately (it's more like a fire forget)
@ -491,12 +491,12 @@ impl SpircTask {
},
cmd = async { commands?.recv().await }, if commands.is_some() => if let Some(cmd) = cmd {
if let Err(e) = self.handle_command(cmd).await {
debug!("could not dispatch command: {}", e);
debug!("could not dispatch command: {e}");
}
},
event = async { player_events?.recv().await }, if player_events.is_some() => if let Some(event) = event {
if let Err(e) = self.handle_player_event(event) {
error!("could not dispatch player event: {}", e);
error!("could not dispatch player event: {e}");
}
},
_ = async { sleep(UPDATE_STATE_DELAY).await }, if self.update_state => {
@ -606,7 +606,7 @@ impl SpircTask {
}
async fn handle_command(&mut self, cmd: SpircCommand) -> Result<(), Error> {
trace!("Received SpircCommand::{:?}", cmd);
trace!("Received SpircCommand::{cmd:?}");
match cmd {
SpircCommand::Shutdown => {
trace!("Received SpircCommand::Shutdown");
@ -618,16 +618,15 @@ impl SpircTask {
}
}
SpircCommand::Activate if !self.connect_state.is_active() => {
trace!("Received SpircCommand::{:?}", cmd);
trace!("Received SpircCommand::{cmd:?}");
self.handle_activate();
return self.notify().await;
}
SpircCommand::Activate => warn!(
"SpircCommand::{:?} will be ignored while already active",
cmd
),
SpircCommand::Activate => {
warn!("SpircCommand::{cmd:?} will be ignored while already active")
}
_ if !self.connect_state.is_active() => {
warn!("SpircCommand::{:?} will be ignored while Not Active", cmd)
warn!("SpircCommand::{cmd:?} will be ignored while Not Active")
}
SpircCommand::Disconnect { pause } => {
if pause {
@ -787,7 +786,7 @@ impl SpircTask {
}
async fn handle_connection_id_update(&mut self, connection_id: String) -> Result<(), Error> {
trace!("Received connection ID update: {:?}", connection_id);
trace!("Received connection ID update: {connection_id:?}");
self.session.set_connection_id(&connection_id);
let cluster = match self
@ -837,7 +836,7 @@ impl SpircTask {
}
fn handle_user_attributes_update(&mut self, update: UserAttributesUpdate) {
trace!("Received attributes update: {:#?}", update);
trace!("Received attributes update: {update:#?}");
let attributes: UserAttributes = update
.pairs
.iter()
@ -863,12 +862,7 @@ impl SpircTask {
};
self.session.set_user_attribute(key, new_value);
trace!(
"Received attribute mutation, {} was {} is now {}",
key,
old_value,
new_value
);
trace!("Received attribute mutation, {key} was {old_value} is now {new_value}");
if key == "filter-explicit-content" && new_value == "1" {
self.player
@ -882,10 +876,7 @@ impl SpircTask {
self.add_autoplay_resolving_when_required()
}
} else {
trace!(
"Received attribute mutation for {} but key was not found!",
key
);
trace!("Received attribute mutation for {key} but key was not found!");
}
}
}
@ -1743,7 +1734,7 @@ impl SpircTask {
}
fn set_volume(&mut self, volume: u16) {
debug!("SpircTask::set_volume({})", volume);
debug!("SpircTask::set_volume({volume})");
let old_volume = self.connect_state.device_info().volume;
let new_volume = volume as u32;

View file

@ -108,7 +108,7 @@ impl ApResolver {
if inner.data.is_any_empty() {
warn!("Failed to resolve all access points, using fallbacks");
if let Some(error) = error {
warn!("Resolve access points error: {}", error);
warn!("Resolve access points error: {error}");
}
let fallback = self.parse_resolve_to_access_points(ApResolveData::fallback());

View file

@ -70,11 +70,7 @@ impl AudioKeyManager {
.map_err(|_| AudioKeyError::Channel)?
}
_ => {
trace!(
"Did not expect {:?} AES key packet with data {:#?}",
cmd,
data
);
trace!("Did not expect {cmd:?} AES key packet with data {data:#?}");
return Err(AudioKeyError::Packet(cmd as u8).into());
}
}

View file

@ -141,7 +141,7 @@ impl FsSizeLimiter {
let list_dir = match fs::read_dir(path) {
Ok(list_dir) => list_dir,
Err(e) => {
warn!("Could not read directory {:?} in cache dir: {}", path, e);
warn!("Could not read directory {path:?} in cache dir: {e}");
return;
}
};
@ -150,7 +150,7 @@ impl FsSizeLimiter {
let entry = match entry {
Ok(entry) => entry,
Err(e) => {
warn!("Could not directory {:?} in cache dir: {}", path, e);
warn!("Could not directory {path:?} in cache dir: {e}");
return;
}
};
@ -166,7 +166,7 @@ impl FsSizeLimiter {
limiter.add(&path, size, access_time);
}
Err(e) => {
warn!("Could not read file {:?} in cache dir: {}", path, e)
warn!("Could not read file {path:?} in cache dir: {e}")
}
}
}
@ -213,7 +213,7 @@ impl FsSizeLimiter {
let res = fs::remove_file(&file);
if let Err(e) = res {
warn!("Could not remove file {:?} from cache dir: {}", file, e);
warn!("Could not remove file {file:?} from cache dir: {e}");
last_error = Some(e);
} else {
count += 1;
@ -221,7 +221,7 @@ impl FsSizeLimiter {
}
if count > 0 {
info!("Removed {} cache files.", count);
info!("Removed {count} cache files.");
}
if let Some(err) = last_error {
@ -317,7 +317,7 @@ impl Cache {
// If the file did not exist, the file was probably not written
// before. Otherwise, log the error.
if e.kind != ErrorKind::NotFound {
warn!("Error reading credentials from cache: {}", e);
warn!("Error reading credentials from cache: {e}");
}
None
}
@ -332,7 +332,7 @@ impl Cache {
});
if let Err(e) = result {
warn!("Cannot save credentials to cache: {}", e)
warn!("Cannot save credentials to cache: {e}")
}
}
}
@ -351,7 +351,7 @@ impl Cache {
Ok(v) => Some(v),
Err(e) => {
if e.kind != ErrorKind::NotFound {
warn!("Error reading volume from cache: {}", e);
warn!("Error reading volume from cache: {e}");
}
None
}
@ -362,7 +362,7 @@ impl Cache {
if let Some(ref location) = self.volume_location {
let result = File::create(location).and_then(|mut file| write!(file, "{volume}"));
if let Err(e) = result {
warn!("Cannot save volume to cache: {}", e);
warn!("Cannot save volume to cache: {e}");
}
}
}
@ -375,7 +375,7 @@ impl Cache {
path
}),
Err(e) => {
warn!("Invalid FileId: {}", e);
warn!("Invalid FileId: {e}");
None
}
}
@ -387,14 +387,14 @@ impl Cache {
Ok(file) => {
if let Some(limiter) = self.size_limiter.as_deref() {
if !limiter.touch(&path) {
error!("limiter could not touch {:?}", path);
error!("limiter could not touch {path:?}");
}
}
Some(file)
}
Err(e) => {
if e.kind() != io::ErrorKind::NotFound {
warn!("Error reading file from cache: {}", e)
warn!("Error reading file from cache: {e}")
}
None
}

View file

@ -73,7 +73,7 @@ impl CdnUrl {
let cdn_url = Self { file_id, urls };
trace!("Resolved CDN storage: {:#?}", cdn_url);
trace!("Resolved CDN storage: {cdn_url:#?}");
Ok(cdn_url)
}

View file

@ -186,11 +186,7 @@ pub async fn authenticate(
Err(error_data.into())
}
_ => {
trace!(
"Did not expect {:?} AES key packet with data {:#?}",
cmd,
data
);
trace!("Did not expect {cmd:?} AES key packet with data {data:#?}");
Err(AuthenticationError::Packet(cmd))
}
};

View file

@ -89,7 +89,7 @@ impl Responder {
.to_string();
if let Err(e) = self.tx.send(WsMessage::Text(response.into())) {
warn!("Wasn't able to reply to dealer request: {}", e);
warn!("Wasn't able to reply to dealer request: {e}");
}
}
@ -452,7 +452,7 @@ impl Dealer {
if let Some(handle) = self.handle.take() {
if let Err(e) = CancelOnDrop(handle).await {
error!("error aborting dealer operations: {}", e);
error!("error aborting dealer operations: {e}");
}
}
}
@ -524,13 +524,13 @@ async fn connect(
Ok(close_frame) => ws_tx.send(WsMessage::Close(close_frame)).await,
Err(WsError::AlreadyClosed) | Err(WsError::ConnectionClosed) => ws_tx.flush().await,
Err(e) => {
warn!("Dealer finished with an error: {}", e);
warn!("Dealer finished with an error: {e}");
ws_tx.send(WsMessage::Close(None)).await
}
};
if let Err(e) = result {
warn!("Error while closing websocket: {}", e);
warn!("Error while closing websocket: {e}");
}
debug!("Dropping send task");
@ -565,7 +565,7 @@ async fn connect(
_ => (), // tungstenite handles Close and Ping automatically
},
Some(Err(e)) => {
warn!("Websocket connection failed: {}", e);
warn!("Websocket connection failed: {e}");
break;
}
None => {
@ -648,13 +648,13 @@ where
() = shared.closed() => break,
r = t0 => {
if let Err(e) = r {
error!("timeout on task 0: {}", e);
error!("timeout on task 0: {e}");
}
tasks.0.take();
},
r = t1 => {
if let Err(e) = r {
error!("timeout on task 1: {}", e);
error!("timeout on task 1: {e}");
}
tasks.1.take();
}
@ -671,7 +671,7 @@ where
match connect(&url, proxy.as_ref(), &shared).await {
Ok((s, r)) => tasks = (init_task(s), init_task(r)),
Err(e) => {
error!("Error while connecting: {}", e);
error!("Error while connecting: {e}");
tokio::time::sleep(RECONNECT_INTERVAL).await;
}
}

View file

@ -124,7 +124,7 @@ impl HttpClient {
);
let user_agent = HeaderValue::from_str(user_agent_str).unwrap_or_else(|err| {
error!("Invalid user agent <{}>: {}", user_agent_str, err);
error!("Invalid user agent <{user_agent_str}>: {err}");
HeaderValue::from_static(FALLBACK_USER_AGENT)
});
@ -176,7 +176,7 @@ impl HttpClient {
}
pub async fn request(&self, req: Request<Bytes>) -> Result<Response<Incoming>, Error> {
debug!("Requesting {}", req.uri().to_string());
debug!("Requesting {}", req.uri());
// `Request` does not implement `Clone` because its `Body` may be a single-shot stream.
// As correct as that may be technically, we now need all this boilerplate to clone it

View file

@ -199,7 +199,7 @@ impl Login5Manager {
inner.auth_token.clone()
});
trace!("Got auth token: {:?}", auth_token);
trace!("Got auth token: {auth_token:?}");
token.ok_or(Login5Error::NoStoredCredentials.into())
}

View file

@ -131,12 +131,12 @@ impl MercuryManager {
Ok(mut sub) => {
let sub_uri = sub.take_uri();
debug!("subscribed sub_uri={}", sub_uri);
debug!("subscribed sub_uri={sub_uri}");
inner.subscriptions.push((sub_uri, tx.clone()));
}
Err(e) => {
error!("could not subscribe to {}: {}", uri, e);
error!("could not subscribe to {uri}: {e}");
}
}
}
@ -163,7 +163,7 @@ impl MercuryManager {
manager.lock(move |inner| {
if !inner.invalid {
debug!("listening to uri={}", uri);
debug!("listening to uri={uri}");
inner.subscriptions.push((uri, tx));
}
});
@ -283,14 +283,14 @@ impl MercuryManager {
Ok(())
} else {
debug!("unknown subscription uri={}", &response.uri);
trace!("response pushed over Mercury: {:?}", response);
trace!("response pushed over Mercury: {response:?}");
Err(MercuryError::Response(response).into())
}
} else if let Some(cb) = pending.callback {
cb.send(Ok(response)).map_err(|_| MercuryError::Channel)?;
Ok(())
} else {
error!("can't handle Mercury response: {:?}", response);
error!("can't handle Mercury response: {response:?}");
Err(MercuryError::Response(response).into())
}
}

View file

@ -273,7 +273,7 @@ impl Session {
let session_weak = self.weak();
tokio::spawn(async move {
if let Err(e) = sender_task.await {
error!("{}", e);
error!("{e}");
if let Some(session) = session_weak.try_upgrade() {
if !session.is_invalid() {
session.shutdown();
@ -360,7 +360,7 @@ impl Session {
fn check_catalogue(attributes: &UserAttributes) {
if let Some(account_type) = attributes.get("type") {
if account_type != "premium" {
error!("librespot does not support {:?} accounts.", account_type);
error!("librespot does not support {account_type:?} accounts.");
info!("Please support Spotify and your artists and sign up for a premium account.");
// TODO: logout instead of exiting
@ -566,7 +566,7 @@ impl KeepAliveState {
.map(|t| t.as_secs_f64())
.unwrap_or(f64::INFINITY);
trace!("keep-alive state: {:?}, timeout in {:.1}", self, delay);
trace!("keep-alive state: {self:?}, timeout in {delay:.1}");
}
}
@ -619,7 +619,7 @@ where
let cmd = match packet_type {
Some(cmd) => cmd,
None => {
trace!("Ignoring unknown packet {:x}", cmd);
trace!("Ignoring unknown packet {cmd:x}");
return Err(SessionError::Packet(cmd).into());
}
};
@ -667,7 +667,7 @@ where
}
Some(CountryCode) => {
let country = String::from_utf8(data.as_ref().to_owned())?;
info!("Country: {:?}", country);
info!("Country: {country:?}");
session.0.data.write().user_data.country = country;
Ok(())
}
@ -710,7 +710,7 @@ where
}
}
trace!("Received product info: {:#?}", user_attributes);
trace!("Received product info: {user_attributes:#?}");
Session::check_catalogue(&user_attributes);
session.0.data.write().user_data.attributes = user_attributes;
@ -721,7 +721,7 @@ where
| Some(UnknownDataAllZeros)
| Some(LicenseVersion) => Ok(()),
_ => {
trace!("Ignoring {:?} packet with data {:#?}", cmd, data);
trace!("Ignoring {cmd:?} packet with data {data:#?}");
Err(SessionError::Packet(cmd as u8).into())
}
}
@ -749,7 +749,7 @@ where
Poll::Ready(Some(Ok((cmd, data)))) => {
let result = self.as_mut().dispatch(&session, cmd, data);
if let Err(e) = result {
debug!("could not dispatch command: {}", e);
debug!("could not dispatch command: {e}");
}
}
Poll::Ready(None) => {

View file

@ -7,7 +7,7 @@ use crate::proxytunnel;
pub async fn connect(host: &str, port: u16, proxy: Option<&Url>) -> io::Result<TcpStream> {
let socket = if let Some(proxy_url) = proxy {
info!("Using proxy \"{}\"", proxy_url);
info!("Using proxy \"{proxy_url}\"");
let socket_addr = proxy_url.socket_addrs(|| None).and_then(|addrs| {
addrs.into_iter().next().ok_or_else(|| {

View file

@ -311,20 +311,12 @@ impl SpClient {
continue;
}
Err(e) => {
trace!(
"Answer not accepted {}/{}: {}",
count,
MAX_TRIES,
e
);
trace!("Answer not accepted {count}/{MAX_TRIES}: {e}");
}
}
}
Err(e) => trace!(
"Unable to solve hash cash challenge {}/{}: {}",
count,
MAX_TRIES,
e
"Unable to solve hash cash challenge {count}/{MAX_TRIES}: {e}"
),
}
@ -373,7 +365,7 @@ impl SpClient {
inner.client_token = Some(client_token);
});
trace!("Got client token: {:?}", granted_token);
trace!("Got client token: {granted_token:?}");
Ok(access_token)
}
@ -542,7 +534,7 @@ impl SpClient {
}
}
debug!("Error was: {:?}", last_response);
debug!("Error was: {last_response:?}");
}
last_response

View file

@ -86,8 +86,7 @@ impl TokenProvider {
}
trace!(
"Requested token in scopes {:?} unavailable or expired, requesting new token.",
scopes
"Requested token in scopes {scopes:?} unavailable or expired, requesting new token."
);
let query_uri = format!(
@ -100,7 +99,7 @@ impl TokenProvider {
let response = request.await?;
let data = response.payload.first().ok_or(TokenError::Empty)?.to_vec();
let token = Token::from_json(String::from_utf8(data)?)?;
trace!("Got token: {:#?}", token);
trace!("Got token: {token:#?}");
self.lock(|inner| inner.tokens.push(token.clone()));
Ok(token)
}

View file

@ -419,7 +419,7 @@ fn launch_libmdns(
};
if let Err(e) = inner() {
log::error!("libmdns error: {}", e);
log::error!("libmdns error: {e}");
let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e));
}
});

View file

@ -170,7 +170,7 @@ impl RequestHandler {
.map_err(|_| DiscoveryError::HmacError(base_key.to_vec()))?;
h.update(encrypted);
if h.verify_slice(cksum).is_err() {
warn!("Login error for user {:?}: MAC mismatch", username);
warn!("Login error for user {username:?}: MAC mismatch");
let result = json!({
"status": 102,
"spotifyError": 1,
@ -314,7 +314,7 @@ impl DiscoveryServer {
discovery
.clone()
.handle(request)
.inspect_err(|e| error!("could not handle discovery request: {}", e))
.inspect_err(|e| error!("could not handle discovery request: {e}"))
.and_then(|x| async move { Ok(x) })
.map(Result::unwrap) // guaranteed by `and_then` above
});

View file

@ -1,7 +1,7 @@
use librespot::{
connect::{ConnectConfig, LoadRequest, LoadRequestOptions, Spirc},
core::{
authentication::Credentials, cache::Cache, config::SessionConfig, session::Session, Error,
Error, authentication::Credentials, cache::Cache, config::SessionConfig, session::Session,
},
playback::mixer::MixerConfig,
playback::{

View file

@ -78,10 +78,7 @@ impl Playlist {
let length = tracks.len();
let expected_length = self.length as usize;
if length != expected_length {
warn!(
"Got {} tracks, but the list should contain {} tracks.",
length, expected_length,
);
warn!("Got {length} tracks, but the list should contain {expected_length} tracks.",);
}
tracks

View file

@ -18,7 +18,6 @@ use oauth2::{
EndpointSet, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl,
};
use oauth2::{EmptyExtraTokenFields, PkceCodeVerifier, RefreshToken, StandardTokenResponse};
use reqwest;
use std::io;
use std::sync::mpsc;
use std::time::{Duration, Instant};
@ -156,7 +155,7 @@ fn get_authcode_listener(
addr: socket_address,
e,
})?;
info!("OAuth server listening on {:?}", socket_address);
info!("OAuth server listening on {socket_address:?}");
// The server will terminate itself after collecting the first code.
let mut stream = listener

View file

@ -226,7 +226,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
let buffer_size = {
let max = match hwp.get_buffer_size_max() {
Err(e) => {
trace!("Error getting the device's max Buffer size: {}", e);
trace!("Error getting the device's max Buffer size: {e}");
ZERO_FRAMES
}
Ok(s) => s,
@ -234,7 +234,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
let min = match hwp.get_buffer_size_min() {
Err(e) => {
trace!("Error getting the device's min Buffer size: {}", e);
trace!("Error getting the device's min Buffer size: {e}");
ZERO_FRAMES
}
Ok(s) => s,
@ -246,11 +246,11 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
.find(|f| (min..=max).contains(f))
{
Some(size) => {
trace!("Desired Frames per Buffer: {:?}", size);
trace!("Desired Frames per Buffer: {size:?}");
match hwp.set_buffer_size_near(size) {
Err(e) => {
trace!("Error setting the device's Buffer size: {}", e);
trace!("Error setting the device's Buffer size: {e}");
ZERO_FRAMES
}
Ok(s) => s,
@ -267,17 +267,9 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
};
if buffer_size == ZERO_FRAMES {
trace!(
"Desired Buffer Frame range: {:?} - {:?}",
MIN_BUFFER,
MAX_BUFFER
);
trace!("Desired Buffer Frame range: {MIN_BUFFER:?} - {MAX_BUFFER:?}",);
trace!(
"Actual Buffer Frame range as reported by the device: {:?} - {:?}",
min,
max
);
trace!("Actual Buffer Frame range as reported by the device: {min:?} - {max:?}",);
}
buffer_size
@ -289,7 +281,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
} else {
let max = match hwp.get_period_size_max() {
Err(e) => {
trace!("Error getting the device's max Period size: {}", e);
trace!("Error getting the device's max Period size: {e}");
ZERO_FRAMES
}
Ok(s) => s,
@ -297,7 +289,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
let min = match hwp.get_period_size_min() {
Err(e) => {
trace!("Error getting the device's min Period size: {}", e);
trace!("Error getting the device's min Period size: {e}");
ZERO_FRAMES
}
Ok(s) => s,
@ -312,11 +304,11 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
.find(|f| (min..=max).contains(f))
{
Some(size) => {
trace!("Desired Frames per Period: {:?}", size);
trace!("Desired Frames per Period: {size:?}");
match hwp.set_period_size_near(size, ValueOr::Nearest) {
Err(e) => {
trace!("Error setting the device's Period size: {}", e);
trace!("Error setting the device's Period size: {e}");
ZERO_FRAMES
}
Ok(s) => s,
@ -334,20 +326,14 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
};
if period_size == ZERO_FRAMES {
trace!("Buffer size: {:?}", buffer_size);
trace!("Buffer size: {buffer_size:?}");
trace!(
"Desired Period Frame range: {:?} (Buffer size / {:?}) - {:?} (Buffer size / {:?})",
min_period,
MIN_PERIOD_DIVISOR,
max_period,
MAX_PERIOD_DIVISOR,
"Desired Period Frame range: {min_period:?} (Buffer size / {MIN_PERIOD_DIVISOR:?}) - {max_period:?} (Buffer size / {MAX_PERIOD_DIVISOR:?})",
);
trace!(
"Actual Period Frame range as reported by the device: {:?} - {:?}",
min,
max
"Actual Period Frame range as reported by the device: {min:?} - {max:?}",
);
}
@ -381,14 +367,14 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
pcm.sw_params(&swp).map_err(AlsaError::Pcm)?;
trace!("Actual Frames per Buffer: {:?}", frames_per_buffer);
trace!("Actual Frames per Period: {:?}", frames_per_period);
trace!("Actual Frames per Buffer: {frames_per_buffer:?}");
trace!("Actual Frames per Period: {frames_per_period:?}");
// Let ALSA do the math for us.
pcm.frames_to_bytes(frames_per_period) as usize
};
trace!("Period Buffer size in bytes: {:?}", bytes_per_period);
trace!("Period Buffer size in bytes: {bytes_per_period:?}");
Ok((pcm, bytes_per_period))
}
@ -401,7 +387,7 @@ impl Open for AlsaSink {
exit(0);
}
Err(e) => {
error!("{}", e);
error!("{e}");
exit(1);
}
},
@ -410,7 +396,7 @@ impl Open for AlsaSink {
}
.to_string();
info!("Using AlsaSink with format: {:?}", format);
info!("Using AlsaSink with format: {format:?}");
Self {
pcm: None,
@ -500,10 +486,7 @@ impl AlsaSink {
Err(e) => {
// Capture and log the original error as a warning, and then try to recover.
// If recovery fails then forward that error back to player.
warn!(
"Error writing from AlsaSink buffer to PCM, trying to recover, {}",
e
);
warn!("Error writing from AlsaSink buffer to PCM, trying to recover, {e}");
pcm.try_recover(e, false).map_err(AlsaError::OnWrite)
}

View file

@ -48,7 +48,7 @@ impl Open for StdoutSink {
exit(0);
}
info!("Using StdoutSink (pipe) with format: {:?}", format);
info!("Using StdoutSink (pipe) with format: {format:?}");
Self {
output: None,

View file

@ -72,7 +72,7 @@ impl Open for SubprocessSink {
exit(0);
}
info!("Using SubprocessSink with format: {:?}", format);
info!("Using SubprocessSink with format: {format:?}");
Self {
shell_command,

View file

@ -33,10 +33,7 @@ impl MappedCtrl for VolumeCtrl {
}
} else {
// Ensure not to return -inf or NaN due to division by zero.
error!(
"{:?} does not work with 0 dB range, using linear mapping instead",
self
);
error!("{self:?} does not work with 0 dB range, using linear mapping instead");
normalized_volume
};
@ -67,10 +64,7 @@ impl MappedCtrl for VolumeCtrl {
}
} else {
// Ensure not to return -inf or NaN due to division by zero.
error!(
"{:?} does not work with 0 dB range, using linear mapping instead",
self
);
error!("{self:?} does not work with 0 dB range, using linear mapping instead");
mapped_volume
};
@ -88,10 +82,10 @@ impl MappedCtrl for VolumeCtrl {
fn set_db_range(&mut self, new_db_range: f64) {
match self {
Self::Cubic(ref mut db_range) | Self::Log(ref mut db_range) => *db_range = new_db_range,
_ => error!("Invalid to set dB range for volume control type {:?}", self),
_ => error!("Invalid to set dB range for volume control type {self:?}"),
}
debug!("Volume control is now {:?}", self)
debug!("Volume control is now {self:?}")
}
fn range_ok(&self) -> bool {

View file

@ -17,7 +17,7 @@ pub struct SoftMixer {
impl Mixer for SoftMixer {
fn open(config: MixerConfig) -> Result<Self, Error> {
let volume_ctrl = config.volume_ctrl;
info!("Mixing with softvol and volume control: {:?}", volume_ctrl);
info!("Mixing with softvol and volume control: {volume_ctrl:?}");
Ok(Self {
volume: Arc::new(AtomicU64::new(f64::to_bits(0.5))),

View file

@ -324,8 +324,7 @@ impl NormalisationData {
let newpos = file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))?;
if newpos != SPOTIFY_NORMALIZATION_HEADER_START_OFFSET {
error!(
"NormalisationData::parse_from_file seeking to {} but position is now {}",
SPOTIFY_NORMALIZATION_HEADER_START_OFFSET, newpos
"NormalisationData::parse_from_file seeking to {SPOTIFY_NORMALIZATION_HEADER_START_OFFSET} but position is now {newpos}"
);
error!("Falling back to default (non-track and non-album) normalisation data.");
@ -396,8 +395,7 @@ impl NormalisationData {
let limiting_db = factor_db + config.normalisation_threshold_dbfs.abs();
warn!(
"This track may exceed dBFS by {:.2} dB and be subject to {:.2} dB of dynamic limiting at its peak.",
factor_db, limiting_db
"This track may exceed dBFS by {factor_db:.2} dB and be subject to {limiting_db:.2} dB of dynamic limiting at its peak."
);
} else if factor > threshold_ratio {
let limiting_db = gain_db
@ -405,15 +403,14 @@ impl NormalisationData {
+ config.normalisation_threshold_dbfs.abs();
info!(
"This track may be subject to {:.2} dB of dynamic limiting at its peak.",
limiting_db
"This track may be subject to {limiting_db:.2} dB of dynamic limiting at its peak."
);
}
factor
};
debug!("Normalisation Data: {:?}", data);
debug!("Normalisation Data: {data:?}");
debug!(
"Calculated Normalisation Factor for {:?}: {:.2}%",
config.normalisation_type,
@ -464,7 +461,7 @@ impl Player {
let handle = thread::spawn(move || {
let player_id = PLAYER_COUNTER.fetch_add(1, Ordering::AcqRel);
debug!("new Player [{}]", player_id);
debug!("new Player [{player_id}]");
let converter = Converter::new(config.ditherer);
@ -517,7 +514,7 @@ impl Player {
fn command(&self, cmd: PlayerCommand) {
if let Some(commands) = self.commands.as_ref() {
if let Err(e) = commands.send(cmd) {
error!("Player Commands Error: {}", e);
error!("Player Commands Error: {e}");
}
}
}
@ -636,7 +633,7 @@ impl Drop for Player {
self.commands = None;
if let Some(handle) = self.thread_handle.take() {
if let Err(e) = handle.join() {
error!("Player thread Error: {:?}", e);
error!("Player thread Error: {e:?}");
}
}
}
@ -787,10 +784,7 @@ impl PlayerState {
};
}
_ => {
error!(
"Called playing_to_end_of_track in non-playing state: {:?}",
new_state
);
error!("Called playing_to_end_of_track in non-playing state: {new_state:?}");
exit(1);
}
}
@ -832,10 +826,7 @@ impl PlayerState {
};
}
_ => {
error!(
"PlayerState::paused_to_playing in invalid state: {:?}",
new_state
);
error!("PlayerState::paused_to_playing in invalid state: {new_state:?}");
exit(1);
}
}
@ -876,10 +867,7 @@ impl PlayerState {
};
}
_ => {
error!(
"PlayerState::playing_to_paused in invalid state: {:?}",
new_state
);
error!("PlayerState::playing_to_paused in invalid state: {new_state:?}");
exit(1);
}
}
@ -894,7 +882,7 @@ struct PlayerTrackLoader {
impl PlayerTrackLoader {
async fn find_available_alternative(&self, audio_item: AudioItem) -> Option<AudioItem> {
if let Err(e) = audio_item.availability {
error!("Track is unavailable: {}", e);
error!("Track is unavailable: {e}");
None
} else if !audio_item.files.is_empty() {
Some(audio_item)
@ -958,7 +946,7 @@ impl PlayerTrackLoader {
}
},
Err(e) => {
error!("Unable to load audio item: {:?}", e);
error!("Unable to load audio item: {e:?}");
return None;
}
};
@ -1026,7 +1014,7 @@ impl PlayerTrackLoader {
let encrypted_file = match encrypted_file.await {
Ok(encrypted_file) => encrypted_file,
Err(e) => {
error!("Unable to load encrypted file: {:?}", e);
error!("Unable to load encrypted file: {e:?}");
return None;
}
};
@ -1041,7 +1029,7 @@ impl PlayerTrackLoader {
let key = match self.session.audio_key().request(spotify_id, file_id).await {
Ok(key) => Some(key),
Err(e) => {
warn!("Unable to load key, continuing without decryption: {}", e);
warn!("Unable to load key, continuing without decryption: {e}");
None
}
};
@ -1064,7 +1052,7 @@ impl PlayerTrackLoader {
) {
Ok(audio_file) => audio_file,
Err(e) => {
error!("PlayerTrackLoader::load_track error opening subfile: {}", e);
error!("PlayerTrackLoader::load_track error opening subfile: {e}");
return None;
}
};
@ -1098,10 +1086,7 @@ impl PlayerTrackLoader {
let mut decoder = match decoder_type {
Ok(decoder) => decoder,
Err(e) if is_cached => {
warn!(
"Unable to read cached audio file: {}. Trying to download it.",
e
);
warn!("Unable to read cached audio file: {e}. Trying to download it.");
match self.session.cache() {
Some(cache) => {
@ -1120,7 +1105,7 @@ impl PlayerTrackLoader {
continue;
}
Err(e) => {
error!("Unable to read audio file: {}", e);
error!("Unable to read audio file: {e}");
return None;
}
};
@ -1130,7 +1115,7 @@ impl PlayerTrackLoader {
// If the position is invalid just start from
// the beginning of the track.
let position_ms = if position_ms > duration_ms {
warn!("Invalid start position of {} ms exceeds track's duration of {} ms, starting track from the beginning", position_ms, duration_ms);
warn!("Invalid start position of {position_ms} ms exceeds track's duration of {duration_ms} ms, starting track from the beginning");
0
} else {
position_ms
@ -1144,8 +1129,7 @@ impl PlayerTrackLoader {
Ok(new_position_ms) => new_position_ms,
Err(e) => {
error!(
"PlayerTrackLoader::load_track error seeking to starting position {}: {}",
position_ms, e
"PlayerTrackLoader::load_track error seeking to starting position {position_ms}: {e}"
);
return None;
}
@ -1195,7 +1179,7 @@ impl Future for PlayerInternal {
if let Some(cmd) = cmd {
if let Err(e) = self.handle_command(cmd) {
error!("Error handling command: {}", e);
error!("Error handling command: {e}");
}
}
@ -1225,8 +1209,7 @@ impl Future for PlayerInternal {
}
Poll::Ready(Err(e)) => {
error!(
"Skipping to next track, unable to load track <{:?}>: {:?}",
track_id, e
"Skipping to next track, unable to load track <{track_id:?}>: {e:?}"
);
self.send_event(PlayerEvent::Unavailable {
track_id,
@ -1253,7 +1236,7 @@ impl Future for PlayerInternal {
};
}
Poll::Ready(Err(_)) => {
debug!("Unable to preload {:?}", track_id);
debug!("Unable to preload {track_id:?}");
self.preload = PlayerPreload::None;
// Let Spirc know that the track was unavailable.
if let PlayerState::Playing {
@ -1368,7 +1351,7 @@ impl Future for PlayerInternal {
}
}
Err(e) => {
error!("Skipping to next track, unable to decode samples for track <{:?}>: {:?}", track_id, e);
error!("Skipping to next track, unable to decode samples for track <{track_id:?}>: {e:?}");
self.send_event(PlayerEvent::EndOfTrack {
track_id,
play_request_id,
@ -1381,7 +1364,7 @@ impl Future for PlayerInternal {
self.handle_packet(result, normalisation_factor);
}
Err(e) => {
error!("Skipping to next track, unable to get next packet for track <{:?}>: {:?}", track_id, e);
error!("Skipping to next track, unable to get next packet for track <{track_id:?}>: {e:?}");
self.send_event(PlayerEvent::EndOfTrack {
track_id,
play_request_id,
@ -1443,7 +1426,7 @@ impl PlayerInternal {
match self.sink.start() {
Ok(()) => self.sink_status = SinkStatus::Running,
Err(e) => {
error!("{}", e);
error!("{e}");
self.handle_pause();
}
}
@ -1466,7 +1449,7 @@ impl PlayerInternal {
}
}
Err(e) => {
error!("{}", e);
error!("{e}");
exit(1);
}
}
@ -1694,7 +1677,7 @@ impl PlayerInternal {
}
if let Err(e) = self.sink.write(packet, &mut self.converter) {
error!("{}", e);
error!("{e}");
self.handle_pause();
}
}
@ -2085,7 +2068,7 @@ impl PlayerInternal {
});
}
}
Err(e) => error!("PlayerInternal::handle_command_seek error: {}", e),
Err(e) => error!("PlayerInternal::handle_command_seek error: {e}"),
}
} else {
error!("Player::seek called from invalid state: {:?}", self.state);
@ -2107,7 +2090,7 @@ impl PlayerInternal {
}
fn handle_command(&mut self, cmd: PlayerCommand) -> PlayerResult {
debug!("command={:?}", cmd);
debug!("command={cmd:?}");
match cmd {
PlayerCommand::Load {
track_id,

View file

@ -5,18 +5,18 @@ use librespot::playback::mixer::alsamixer::AlsaMixer;
use librespot::{
connect::{ConnectConfig, Spirc},
core::{
authentication::Credentials, cache::Cache, config::DeviceType, version, Session,
SessionConfig,
Session, SessionConfig, authentication::Credentials, cache::Cache, config::DeviceType,
version,
},
discovery::DnsSdServiceBuilder,
playback::{
audio_backend::{self, SinkBuilder, BACKENDS},
audio_backend::{self, BACKENDS, SinkBuilder},
config::{
AudioFormat, Bitrate, NormalisationMethod, NormalisationType, PlayerConfig, VolumeCtrl,
},
dither,
mixer::{self, MixerConfig, MixerFn},
player::{coefficient_to_duration, duration_to_coefficient, Player},
player::{Player, coefficient_to_duration, duration_to_coefficient},
},
};
use librespot_oauth::OAuthClientBuilder;
@ -24,6 +24,7 @@ use log::{debug, error, info, trace, warn};
use sha1::{Digest, Sha1};
use std::{
env,
ffi::OsStr,
fs::create_dir_all,
ops::RangeInclusive,
path::{Path, PathBuf},
@ -34,10 +35,11 @@ use std::{
};
use sysinfo::{ProcessesToUpdate, System};
use thiserror::Error;
use tokio::sync::Semaphore;
use url::Url;
mod player_event_handler;
use player_event_handler::{run_program_on_sink_events, EventHandler};
use player_event_handler::{EventHandler, run_program_on_sink_events};
fn device_id(name: &str) -> String {
HEXLOWER.encode(&Sha1::digest(name.as_bytes()))
@ -75,7 +77,9 @@ fn setup_logging(quiet: bool, verbose: bool) {
builder.init();
if verbose && quiet {
warn!("`--verbose` and `--quiet` are mutually exclusive. Logging can not be both verbose and quiet. Using verbose mode.");
warn!(
"`--verbose` and `--quiet` are mutually exclusive. Logging can not be both verbose and quiet. Using verbose mode."
);
}
}
}
@ -219,7 +223,7 @@ struct Setup {
zeroconf_backend: Option<DnsSdServiceBuilder>,
}
fn get_setup() -> Setup {
async fn get_setup() -> Setup {
const VALID_INITIAL_VOLUME_RANGE: RangeInclusive<u16> = 0..=100;
const VALID_VOLUME_RANGE: RangeInclusive<f64> = 0.0..=100.0;
const VALID_NORMALISATION_KNEE_RANGE: RangeInclusive<f64> = 0.0..=10.0;
@ -810,7 +814,9 @@ fn get_setup() -> Setup {
ALSA_MIXER_CONTROL,
] {
if opt_present(a) {
warn!("Alsa specific options have no effect if the alsa backend is not enabled at build time.");
warn!(
"Alsa specific options have no effect if the alsa backend is not enabled at build time."
);
break;
}
}
@ -1196,7 +1202,9 @@ fn get_setup() -> Setup {
empty_string_error_msg(USERNAME, USERNAME_SHORT);
}
if opt_present(PASSWORD) {
error!("Invalid `--{PASSWORD}` / `-{PASSWORD_SHORT}`: Password authentication no longer supported, use OAuth");
error!(
"Invalid `--{PASSWORD}` / `-{PASSWORD_SHORT}`: Password authentication no longer supported, use OAuth"
);
exit(1);
}
match cached_creds {
@ -1265,9 +1273,7 @@ fn get_setup() -> Setup {
if let Some(reason) = no_discovery_reason.as_deref() {
if opt_present(ZEROCONF_PORT) {
warn!(
"With {reason} `--{ZEROCONF_PORT}` / `-{ZEROCONF_PORT_SHORT}` has no effect."
);
warn!("With {reason} `--{ZEROCONF_PORT}` / `-{ZEROCONF_PORT_SHORT}` has no effect.");
}
}
@ -1393,31 +1399,31 @@ fn get_setup() -> Setup {
name.clone()
};
env::set_var("PULSE_PROP_application.name", pulseaudio_name);
set_env_var("PULSE_PROP_application.name", pulseaudio_name).await;
}
if env::var("PULSE_PROP_application.version").is_err() {
env::set_var("PULSE_PROP_application.version", version::SEMVER);
set_env_var("PULSE_PROP_application.version", version::SEMVER).await;
}
if env::var("PULSE_PROP_application.icon_name").is_err() {
env::set_var("PULSE_PROP_application.icon_name", "audio-x-generic");
set_env_var("PULSE_PROP_application.icon_name", "audio-x-generic").await;
}
if env::var("PULSE_PROP_application.process.binary").is_err() {
env::set_var("PULSE_PROP_application.process.binary", "librespot");
set_env_var("PULSE_PROP_application.process.binary", "librespot").await;
}
if env::var("PULSE_PROP_stream.description").is_err() {
env::set_var("PULSE_PROP_stream.description", "Spotify Connect endpoint");
set_env_var("PULSE_PROP_stream.description", "Spotify Connect endpoint").await;
}
if env::var("PULSE_PROP_media.software").is_err() {
env::set_var("PULSE_PROP_media.software", "Spotify");
set_env_var("PULSE_PROP_media.software", "Spotify").await;
}
if env::var("PULSE_PROP_media.role").is_err() {
env::set_var("PULSE_PROP_media.role", "music");
set_env_var("PULSE_PROP_media.role", "music").await;
}
}
@ -1839,6 +1845,23 @@ fn get_setup() -> Setup {
}
}
// Initialize a static semaphore with only one permit, which is used to
// prevent setting environment variables from running in parallel.
static PERMIT: Semaphore = Semaphore::const_new(1);
async fn set_env_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
let permit = PERMIT
.acquire()
.await
.expect("Failed to acquire semaphore permit");
// SAFETY: This is safe because setting the environment variable will wait if the permit is
// already acquired by other callers.
unsafe { env::set_var(key, value) }
// Drop the permit manually, so the compiler doesn't optimize it away as unused variable.
drop(permit);
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
const RUST_BACKTRACE: &str = "RUST_BACKTRACE";
@ -1847,10 +1870,10 @@ async fn main() {
const RECONNECT_RATE_LIMIT: usize = 5;
if env::var(RUST_BACKTRACE).is_err() {
env::set_var(RUST_BACKTRACE, "full")
set_env_var(RUST_BACKTRACE, "full").await;
}
let setup = get_setup();
let setup = get_setup().await;
let mut last_credentials = None;
let mut spirc: Option<Spirc> = None;

View file

@ -14,7 +14,8 @@ pub struct EventHandler {
impl EventHandler {
pub fn new(mut player_events: PlayerEventChannel, onevent: &str) -> Self {
let on_event = onevent.to_string();
let thread_handle = Some(thread::spawn(move || loop {
let thread_handle = Some(thread::spawn(move || {
loop {
match player_events.blocking_recv() {
None => break,
Some(event) => {
@ -22,7 +23,8 @@ impl EventHandler {
match event {
PlayerEvent::PlayRequestIdChanged { play_request_id } => {
env_vars.insert("PLAYER_EVENT", "play_request_id_changed".to_string());
env_vars
.insert("PLAYER_EVENT", "play_request_id_changed".to_string());
env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string());
}
PlayerEvent::TrackChanged { audio_item } => {
@ -31,7 +33,8 @@ impl EventHandler {
warn!("PlayerEvent::TrackChanged: Invalid track id: {e}")
}
Ok(id) => {
env_vars.insert("PLAYER_EVENT", "track_changed".to_string());
env_vars
.insert("PLAYER_EVENT", "track_changed".to_string());
env_vars.insert("TRACK_ID", id);
env_vars.insert("URI", audio_item.uri);
env_vars.insert("NAME", audio_item.name);
@ -45,10 +48,14 @@ impl EventHandler {
.join("\n"),
);
env_vars.insert("LANGUAGE", audio_item.language.join("\n"));
env_vars
.insert("DURATION_MS", audio_item.duration_ms.to_string());
env_vars
.insert("IS_EXPLICIT", audio_item.is_explicit.to_string());
env_vars.insert(
"DURATION_MS",
audio_item.duration_ms.to_string(),
);
env_vars.insert(
"IS_EXPLICIT",
audio_item.is_explicit.to_string(),
);
match audio_item.unique_fields {
UniqueFields::Track {
@ -69,12 +76,16 @@ impl EventHandler {
.collect::<Vec<String>>()
.join("\n"),
);
env_vars
.insert("ALBUM_ARTISTS", album_artists.join("\n"));
env_vars.insert(
"ALBUM_ARTISTS",
album_artists.join("\n"),
);
env_vars.insert("ALBUM", album);
env_vars.insert("POPULARITY", popularity.to_string());
env_vars
.insert("POPULARITY", popularity.to_string());
env_vars.insert("NUMBER", number.to_string());
env_vars.insert("DISC_NUMBER", disc_number.to_string());
env_vars
.insert("DISC_NUMBER", disc_number.to_string());
}
UniqueFields::Episode {
description,
@ -131,13 +142,17 @@ impl EventHandler {
env_vars.insert("TRACK_ID", id);
}
},
PlayerEvent::Preloading { track_id, .. } => match track_id.to_base62() {
Err(e) => warn!("PlayerEvent::Preloading: Invalid track id: {e}"),
PlayerEvent::Preloading { track_id, .. } => {
match track_id.to_base62() {
Err(e) => {
warn!("PlayerEvent::Preloading: Invalid track id: {e}")
}
Ok(id) => {
env_vars.insert("PLAYER_EVENT", "preloading".to_string());
env_vars.insert("TRACK_ID", id);
}
},
}
}
PlayerEvent::TimeToPreloadNextTrack { track_id, .. } => {
match track_id.to_base62() {
Err(e) => warn!(
@ -149,14 +164,19 @@ impl EventHandler {
}
}
}
PlayerEvent::EndOfTrack { track_id, .. } => match track_id.to_base62() {
Err(e) => warn!("PlayerEvent::EndOfTrack: Invalid track id: {e}"),
PlayerEvent::EndOfTrack { track_id, .. } => {
match track_id.to_base62() {
Err(e) => {
warn!("PlayerEvent::EndOfTrack: Invalid track id: {e}")
}
Ok(id) => {
env_vars.insert("PLAYER_EVENT", "end_of_track".to_string());
env_vars.insert("TRACK_ID", id);
}
},
PlayerEvent::Unavailable { track_id, .. } => match track_id.to_base62() {
}
}
PlayerEvent::Unavailable { track_id, .. } => match track_id.to_base62()
{
Err(e) => warn!("PlayerEvent::Unavailable: Invalid track id: {e}"),
Ok(id) => {
env_vars.insert("PLAYER_EVENT", "unavailable".to_string());
@ -188,7 +208,8 @@ impl EventHandler {
warn!("PlayerEvent::PositionCorrection: Invalid track id: {e}")
}
Ok(id) => {
env_vars.insert("PLAYER_EVENT", "position_correction".to_string());
env_vars
.insert("PLAYER_EVENT", "position_correction".to_string());
env_vars.insert("TRACK_ID", id);
env_vars.insert("POSITION_MS", position_ms.to_string());
}
@ -215,7 +236,8 @@ impl EventHandler {
client_brand_name,
client_model_name,
} => {
env_vars.insert("PLAYER_EVENT", "session_client_changed".to_string());
env_vars
.insert("PLAYER_EVENT", "session_client_changed".to_string());
env_vars.insert("CLIENT_ID", client_id);
env_vars.insert("CLIENT_NAME", client_name);
env_vars.insert("CLIENT_BRAND_NAME", client_brand_name);
@ -251,6 +273,7 @@ impl EventHandler {
}
}
}
}
}));
Self { thread_handle }
@ -287,9 +310,7 @@ pub fn run_program_on_sink_events(sink_status: SinkStatus, onevent: &str) {
fn run_program(env_vars: HashMap<&str, String>, onevent: &str) {
let mut v: Vec<&str> = onevent.split_whitespace().collect();
debug!(
"Running {onevent} with environment variables:\n{env_vars:#?}"
);
debug!("Running {onevent} with environment variables:\n{env_vars:#?}");
match Command::new(v.remove(0))
.args(&v)