From 64e41af96fc49a79ab899718b702be09b0e9649b Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Wed, 1 Jan 2025 01:45:36 -0500 Subject: [PATCH] Implemented ListenBrainz Scrobbling --- dmp-core/Cargo.toml | 1 + dmp-core/src/music_controller/connections.rs | 88 +++++++++++++++----- dmp-core/src/music_controller/controller.rs | 11 ++- src/App.css | 7 ++ src/App.tsx | 14 +--- 5 files changed, 85 insertions(+), 36 deletions(-) diff --git a/dmp-core/Cargo.toml b/dmp-core/Cargo.toml index c64b603..290d3cf 100644 --- a/dmp-core/Cargo.toml +++ b/dmp-core/Cargo.toml @@ -40,3 +40,4 @@ itertools = "0.13.0" prismriver = { git = "https://github.com/Dangoware/prismriver.git"} parking_lot = "0.12.3" discord-presence = { version = "1.4.1", features = ["activity_type"] } +listenbrainz = "0.8.1" diff --git a/dmp-core/src/music_controller/connections.rs b/dmp-core/src/music_controller/connections.rs index 9ada884..18d364d 100644 --- a/dmp-core/src/music_controller/connections.rs +++ b/dmp-core/src/music_controller/connections.rs @@ -1,15 +1,17 @@ #![allow(while_true)] -use std::{thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}}; +use std::{sync::Arc, thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}}; use chrono::TimeDelta; use crossbeam::{scope, select}; use crossbeam_channel::{bounded, Receiver}; use discord_presence::Client; +use listenbrainz::ListenBrainz; +use parking_lot::RwLock; use prismriver::State as PrismState; -use crate::music_storage::library::{Song, Tag}; +use crate::{config::Config, music_storage::library::{Song, Tag}}; -use super::controller::Controller; +use super::controller::{Controller, PlaybackInfo}; #[derive(Debug, Clone)] pub(super) enum ConnectionsNotification { @@ -19,6 +21,7 @@ pub(super) enum ConnectionsNotification { }, StateChange(PrismState), SongChange(Song), + EOS, } #[derive(Debug)] @@ -32,7 +35,9 @@ pub(super) struct ControllerConnections { } impl Controller { - pub(super) fn handle_connections(ControllerConnections { + pub(super) fn handle_connections( + config: Arc>, + ControllerConnections { notifications_tx, inner: ConnectionsInput { discord_rpc_client_id @@ -41,35 +46,46 @@ impl Controller { ) { let (dc_state_rx, dc_state_tx) = bounded::(1); let (dc_song_rx, dc_song_tx) = bounded::(1); + let (lb_song_rx, lb_song_tx) = bounded::(1); + let (lb_eos_rx, lb_eos_tx) = bounded::<()>(1); scope(|s| { s.builder().name("Notifications Sorter".to_string()).spawn(|_| { use ConnectionsNotification::*; while true { match notifications_tx.recv().unwrap() { - Playback { position, duration } => { continue; } + Playback { position, duration } => {} StateChange(state) => { dc_state_rx.send(state.clone()).unwrap(); } SongChange(song) => { - dc_song_rx.send(song).unwrap(); + dc_song_rx.send(song.clone()).unwrap(); + lb_song_rx.send(song).unwrap(); + } + EOS => { + lb_eos_rx.send(()).unwrap(); } } } }).unwrap(); if let Some(client_id) = discord_rpc_client_id { - println!("Discord thingy detected"); s.builder().name("Discord RPC Handler".to_string()).spawn(move |_| { Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx); }).unwrap(); }; + + if let Some(token) = config.read().connections.listenbrainz_token.clone() { + s.builder().name("ListenBrainz Handler".to_string()).spawn(move |_| { + Controller::listenbrainz_scrobble(&token, lb_song_tx, lb_eos_tx); + }).unwrap(); + } }).unwrap(); } fn discord_rpc(client_id: u64, song_tx: Receiver, state_tx: Receiver) { - // TODO: Handle seeking position change + // TODO: Handle seeking position change and pause std::thread::spawn(move || { - let mut client = discord_presence::Client::new(client_id); + let mut client = discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None); client.start(); while !Client::is_ready() { sleep(Duration::from_millis(100)); } println!("discord connected"); @@ -97,8 +113,8 @@ impl Controller { *song = Some(song_); now = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards?").as_secs(); } - } - default(Duration::from_millis(4500)) => () + }, + default(Duration::from_millis(99)) => () } client.set_activity(|activity| { @@ -131,17 +147,45 @@ impl Controller { a }.assets(|a| { a.large_text(state.clone()) - }) + }).instance(true) }).unwrap(); - println!("Updated Discord Status"); } }); } + + fn listenbrainz_scrobble(token: &str, song_tx: Receiver, eos_tx: Receiver<()>) { + let mut client = ListenBrainz::new(); + client.authenticate(token).unwrap(); + if !client.is_authenticated() { + return; + } + + let mut song: Option = None; + while true { + let song = &mut song; + let client = &client; + select! { + recv(song_tx) -> res => { + if let Ok(_song) = res { + client.playing_now(_song.get_tag(&Tag::Artist).map_or("", |tag| tag.as_str()), _song.get_tag(&Tag::Title).map_or("", |tag| tag.as_str()), None).unwrap(); + *song = Some(_song); + println!("Song Listening") + } + }, + recv(eos_tx) -> _ => { + if let Some(song) = song { + client.listen(song.get_tag(&Tag::Artist).map_or("", |tag| tag.as_str()), song.get_tag(&Tag::Title).map_or("", |tag| tag.as_str()), None).unwrap(); + println!("Song Scrobbled"); + } + } + } + } + } } #[cfg(test)] mod test_super { - use std::thread::sleep; + use std::thread::{sleep, spawn}; use crossbeam_channel::unbounded; @@ -150,15 +194,17 @@ mod test_super { use super::*; #[test] - fn discord_test() { - let client_id = std::env!("DISCORD_CLIENT_ID").parse::().unwrap(); + fn lb_test() { let (song_rx, song_tx) = unbounded(); - let (_, state_tx) = unbounded(); + let (eos_rx, eos_tx) = unbounded(); - let (_, lib ) = read_config_lib(); + let (config, lib ) = read_config_lib(); song_rx.send(lib.library[0].clone()).unwrap(); - - Controller::discord_rpc(client_id, song_tx, state_tx); - sleep(Duration::from_secs(150)); + spawn(|| { + Controller::listenbrainz_scrobble(config.connections.listenbrainz_token.unwrap().as_str(), song_tx, eos_tx); + }); + sleep(Duration::from_secs(10)); + eos_rx.send(()).unwrap(); + sleep(Duration::from_secs(10)); } } \ No newline at end of file diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index c45c6e7..3079c05 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -278,6 +278,7 @@ impl Controller { let a = scope.spawn({ let queue_mail = queue_mail.clone(); let _notifications_rx = notifications_rx.clone(); + let _config = config.clone(); move || { futures::executor::block_on(async { moro::async_scope!(|scope| { @@ -303,7 +304,7 @@ impl Controller { Controller::library_loop( lib_mail.1, &mut library, - config, + _config, ) .await .unwrap(); @@ -336,9 +337,11 @@ impl Controller { if let Some(inner) = connections { dbg!(&inner); let d = scope.spawn(|| { - Controller::handle_connections( ControllerConnections { - notifications_tx, - inner, + Controller::handle_connections( + config, + ControllerConnections { + notifications_tx, + inner, }); }); } diff --git a/src/App.css b/src/App.css index 864c96c..adf0eb5 100644 --- a/src/App.css +++ b/src/App.css @@ -9,6 +9,7 @@ --baseColor: #101010; --overlayColor: #1e1e1e; --highlightColor: #1f1f1f; + --highlightColor2: #2b2a2c; --playBarColor: #5c4bb9; --lightTextColor: #7a7a6f; --mediumTextColor: #cacab8; @@ -22,6 +23,8 @@ main { width: 100vw; display: flex; flex-direction: column; + overflow-y: hidden; + overflow-x: hidden; } .container { @@ -221,6 +224,10 @@ main { display: flex; } +.queueSong:hover { + background-color: var(--highlightColor2); +} + .queueSongCoverArt { aspect-ratio: 1; object-fit: contain; diff --git a/src/App.tsx b/src/App.tsx index 7ca9321..555c283 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -273,14 +273,6 @@ function Song(props: SongProps) { { Math.round(+props.duration / 60) }: { (+props.duration % 60).toString().padStart(2, "0") }

- - {/* - */} ) } @@ -302,8 +294,8 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) { const pos_ = Array.isArray(info.position) ? info.position![0] : 0; const dur_ = Array.isArray(info.duration) ? info.duration![0] : 0; - setPosition(pos_); - setDuration(dur_); + setPosition(dur_); + setDuration(pos_); let progress = ((dur_/pos_) * 100); setSeekBarSize(progress) }) @@ -401,7 +393,7 @@ function QueueSong({ song, location, index }: QueueSongProps) { } return ( -
+

{ song.tags.TrackTitle }