From d8b0db2ee74af3e8e8f813b0c8353d12c11a5c59 Mon Sep 17 00:00:00 2001 From: LOSSES Don <1384036+Losses@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:23:49 +0800 Subject: [PATCH] feat: A shallow integration of the scrobbling API --- Cargo.lock | 1 + native/hub/Cargo.toml | 1 + native/hub/src/lib.rs | 11 +++++++- native/hub/src/player.rs | 57 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1cfbc834a..30e15d734 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2178,6 +2178,7 @@ dependencies = [ "playback", "prost", "rinf", + "scrobbling", "sea-orm", "sha2", "swift-bridge", diff --git a/native/hub/Cargo.toml b/native/hub/Cargo.toml index 99d6a3073..e248bc55a 100644 --- a/native/hub/Cargo.toml +++ b/native/hub/Cargo.toml @@ -21,6 +21,7 @@ lyric = { path = "../../lyric" } database = { path = "../../database" } analysis = { path = "../../analysis" } playback = { path = "../../playback" } +scrobbling = { path = "../../scrobbling" } lazy_static = "1.5.0" dunce = "1.0.4" log = "0.4.22" diff --git a/native/hub/src/lib.rs b/native/hub/src/lib.rs index b9cd7270d..ebe96395d 100644 --- a/native/hub/src/lib.rs +++ b/native/hub/src/lib.rs @@ -22,6 +22,7 @@ mod system; mod utils; use std::sync::Arc; +use std::time::Duration; use anyhow::Context; use license::validate_license_request; @@ -35,6 +36,7 @@ pub use tokio; use ::playback::player::Player; use ::playback::sfx_player::SfxPlayer; +use ::scrobbling::manager::ScrobblingManager; use crate::analyze::*; use crate::collection::*; @@ -121,13 +123,20 @@ async fn player_loop(path: String, db_connections: DatabaseConnections) { let player = Player::new(Some(main_cancel_token.clone())); let player = Arc::new(Mutex::new(player)); + let scrobbler = ScrobblingManager::new(10, Duration::new(5, 0)); + let scrobbler = Arc::new(Mutex::new(scrobbler)); + let sfx_player = SfxPlayer::new(Some(main_cancel_token.clone())); let sfx_player = Arc::new(Mutex::new(sfx_player)); let main_cancel_token = Arc::new(main_cancel_token); info!("Initializing Player events"); - tokio::spawn(initialize_player(main_db.clone(), player.clone())); + tokio::spawn(initialize_player( + main_db.clone(), + player.clone(), + scrobbler.clone(), + )); info!("Initializing UI events"); select_signal!( diff --git a/native/hub/src/player.rs b/native/hub/src/player.rs index 8f28b0411..7e027bb09 100644 --- a/native/hub/src/player.rs +++ b/native/hub/src/player.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::{bail, Error}; use anyhow::{Context, Result}; @@ -22,12 +23,33 @@ use playback::player::{Player, PlayingItem}; use playback::MediaMetadata; use playback::MediaPlayback; use playback::MediaPosition; +use scrobbling::manager::ScrobblingManager; +use scrobbling::ScrobblingTrack; use crate::{CrashResponse, PlaybackStatus, PlaylistItem, PlaylistUpdate, RealtimeFft}; +pub fn metadata_summary_to_scrobbling_track( + metadata: PlayingItemMetadataSummary, +) -> ScrobblingTrack { + ScrobblingTrack { + artist: metadata.artist, + album: Some(metadata.album), + track: metadata.title, + duration: Some(metadata.duration.clamp(0.0, u32::MAX as f64) as u32), + album_artist: None, + timestamp: Some( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ), + } +} + pub async fn initialize_player( main_db: Arc, player: Arc>, + scrobbler: Arc>, ) -> Result<()> { let status_receiver = player.lock().await.subscribe_status(); let played_through_receiver = player.lock().await.subscribe_played_through(); @@ -43,7 +65,8 @@ pub async fn initialize_player( let manager = Arc::new(Mutex::new(MediaControlManager::new()?)); let os_controller_receiver = manager.lock().await.subscribe_controller_events(); - let dispatcher = PlayingItemActionDispatcher::new(); + let dispatcher = Arc::new(Mutex::new(PlayingItemActionDispatcher::new())); + let dispatcher_for_played_through = Arc::clone(&dispatcher); manager.lock().await.initialize()?; @@ -68,7 +91,12 @@ pub async fn initialize_player( let item_vec = &[item_clone].to_vec(); // Update the cached metadata if the index has changed - let cover_art = match dispatcher.bake_cover_art(&main_db, item_vec).await { + let cover_art = match dispatcher + .lock() + .await + .bake_cover_art(&main_db, item_vec) + .await + { Ok(data) => { let parsed_data = data.values().collect::>(); cached_cover_art = if parsed_data.is_empty() { @@ -82,7 +110,12 @@ pub async fn initialize_player( Err(_) => None, }; - match dispatcher.get_metadata_summary(&main_db, item_vec).await { + match dispatcher + .lock() + .await + .get_metadata_summary(&main_db, item_vec) + .await + { Ok(metadata) => match metadata.first() { Some(metadata) => { cached_meta = Some(metadata.clone()); @@ -181,6 +214,7 @@ pub async fn initialize_player( task::spawn(async move { let main_db = Arc::clone(&main_db_for_played_throudh); + let dispatcher = Arc::clone(&dispatcher_for_played_through); while let Ok(item) = played_through_receiver.recv().await { match item { @@ -195,6 +229,23 @@ pub async fn initialize_player( PlayingItem::IndependentFile(_) => {} PlayingItem::Unknown => {} } + + let metadata = dispatcher + .lock() + .await + .get_metadata_summary(&main_db, [item].as_ref()) + .await; + + if let Ok(metadata) = metadata { + if metadata.is_empty() { + continue; + } + + let metadata: PlayingItemMetadataSummary = metadata[0].clone(); + let track: ScrobblingTrack = metadata_summary_to_scrobbling_track(metadata); + + scrobbler.lock().await.scrobble_all(&track).await; + } } });