aboutsummaryrefslogtreecommitdiffstats
path: root/src/queue.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-05-16 11:43:32 +0200
committersalmonik <raisersoft@proton.me>2026-05-16 11:43:32 +0200
commit04add15691578316ff3da5b5a07d05b314488370 (patch)
tree458b1066001697faa75466d51c7dfd9008ccac16 /src/queue.rs
parentaesthetic changes (diff)
downloadrsplayer-04add15691578316ff3da5b5a07d05b314488370.tar.gz
rsplayer-04add15691578316ff3da5b5a07d05b314488370.zip
Add scrobbling support (Last.fm + ListenBrainz) and rsp settings reload
- Full Last.fm API implementation with MD5 signing (track.updateNowPlaying, track.scrobble) - Full ListenBrainz API implementation (/1/submit-listens for playing_now and single) - ScrobbleState in daemon tracking track, start time, and unix timestamp per play - Scrobble threshold: ≥30s play AND (≥half duration OR ≥4 min) - Duration extraction from symphonia codec params into AudioMetadata and Track - Token validation at daemon startup and on config change - ListenBrainz token valid (user: Salmonium) command to re-read config from disk (avoids token in shell history) - Desktop notifications for token validation results - Empty-credential filtering (empty string = no-op)
Diffstat (limited to 'src/queue.rs')
-rw-r--r--src/queue.rs20
1 files changed, 19 insertions, 1 deletions
diff --git a/src/queue.rs b/src/queue.rs
index d42203f..8f98173 100644
--- a/src/queue.rs
+++ b/src/queue.rs
@@ -13,6 +13,7 @@ pub struct AudioMetadata {
13 pub title: Option<String>, 13 pub title: Option<String>,
14 pub artist: Option<String>, 14 pub artist: Option<String>,
15 pub album: Option<String>, 15 pub album: Option<String>,
16 pub duration_secs: Option<f64>,
16} 17}
17 18
18pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> { 19pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> {
@@ -28,7 +29,22 @@ pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> {
28 .format(&hint, mss, &Default::default(), &Default::default()) 29 .format(&hint, mss, &Default::default(), &Default::default())
29 .ok()?; 30 .ok()?;
30 31
31 let mut metadata = AudioMetadata::default(); 32 // Extract duration from format track's codec parameters
33 let duration_secs = probed
34 .format
35 .tracks()
36 .iter()
37 .find(|t| t.codec_params.n_frames.is_some() && t.codec_params.time_base.is_some())
38 .and_then(|t| {
39 let n_frames = t.codec_params.n_frames?;
40 let tb = t.codec_params.time_base?;
41 Some(n_frames as f64 * tb.numer as f64 / tb.denom as f64)
42 });
43
44 let mut metadata = AudioMetadata {
45 duration_secs,
46 ..Default::default()
47 };
32 48
33 if let Some(meta) = probed.metadata.get() 49 if let Some(meta) = probed.metadata.get()
34 && let Some(rev) = meta.current() 50 && let Some(rev) = meta.current()
@@ -82,6 +98,7 @@ pub struct Track {
82 pub title: Option<String>, 98 pub title: Option<String>,
83 pub artist: Option<String>, 99 pub artist: Option<String>,
84 pub album: Option<String>, 100 pub album: Option<String>,
101 pub duration_secs: Option<f64>,
85} 102}
86 103
87impl Track { 104impl Track {
@@ -93,6 +110,7 @@ impl Track {
93 title: metadata.and_then(|m| m.title.clone()), 110 title: metadata.and_then(|m| m.title.clone()),
94 artist: metadata.and_then(|m| m.artist.clone()), 111 artist: metadata.and_then(|m| m.artist.clone()),
95 album: metadata.and_then(|m| m.album.clone()), 112 album: metadata.and_then(|m| m.album.clone()),
113 duration_secs: metadata.and_then(|m| m.duration_secs),
96 } 114 }
97 } 115 }
98 116