Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src-tauri/src/commands/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ pub async fn logout(state: State<'_, AppState>) -> Result<(), SoneError> {
// Purge scrobbling: in-memory providers + now-playing + retry queue.
// Done before stopping playback so the interrupted track is not scrobbled.
state.scrobble_manager.disconnect_all().await;
state.tidal_reporter.clear().await;

// Stop playback: tear down the pipeline + clear MPRIS/Discord now-playing.
crate::commands::playback::stop_playback(state.inner())
Expand Down
28 changes: 28 additions & 0 deletions src-tauri/src/commands/playback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,30 @@ pub async fn resolve_play_uri(
stream_info.codec, stream_info.manifest.is_some()
);

// Capture the actual served attributes for TIDAL play-reporting.
state
.tidal_reporter
.note_stream_resolved(
track_id,
crate::tidal_report::StreamMeta {
actual_product_id: stream_info.track_id,
quality: stream_info
.audio_quality
.clone()
.unwrap_or_else(|| "LOSSLESS".into()),
audio_mode: stream_info
.audio_mode
.clone()
.unwrap_or_else(|| "STEREO".into()),
presentation: stream_info
.asset_presentation
.clone()
.unwrap_or_else(|| "FULL".into()),
at_ms: (crate::now_secs() as i64) * 1000,
},
)
.await;

let is_dash = stream_info.manifest.is_some();
let uri = if let Some(ref mpd) = stream_info.manifest {
// DASH: pass MPD manifest as a data URI for GStreamer's dashdemux.
Expand Down Expand Up @@ -238,6 +262,7 @@ pub async fn get_video_metadata(
pub async fn pause_track(state: State<'_, AppState>) -> Result<(), SoneError> {
log::debug!("[pause_track]");
let result = state.audio_player.pause().map_err(SoneError::Audio);
state.tidal_reporter.on_pause().await;
state.scrobble_manager.on_pause().await;
result
}
Expand All @@ -246,6 +271,7 @@ pub async fn pause_track(state: State<'_, AppState>) -> Result<(), SoneError> {
pub async fn resume_track(state: State<'_, AppState>) -> Result<(), SoneError> {
log::debug!("[resume_track]");
let result = state.audio_player.resume().map_err(SoneError::Audio);
state.tidal_reporter.on_resume().await;
state.scrobble_manager.on_resume().await;
result
}
Expand All @@ -258,6 +284,7 @@ pub(crate) async fn stop_playback(state: &AppState) -> Result<(), SoneError> {
#[cfg(target_os = "linux")]
state.mpris.send(crate::mpris::MprisCommand::Stop);
state.discord.send(crate::discord::DiscordCommand::Stop);
state.tidal_reporter.on_track_stopped().await;
state.scrobble_manager.on_track_stopped().await;
result
}
Expand Down Expand Up @@ -308,6 +335,7 @@ pub async fn seek_track(state: State<'_, AppState>, position_secs: f32) -> Resul
state.discord.send(crate::discord::DiscordCommand::Seeked {
position_secs: position_secs as f64,
});
state.tidal_reporter.on_seek().await;
state.scrobble_manager.on_seek().await;
result
}
Expand Down
18 changes: 18 additions & 0 deletions src-tauri/src/commands/scrobble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ pub struct TrackStartedPayload {
pub chosen_by_user: bool,
pub isrc: Option<String>,
pub track_id: Option<u64>,
/// Container the play was started from (album/playlist/artist/mix), for
/// TIDAL play-reporting attribution. Absent for radio/single-track plays.
#[serde(default)]
pub source_type: Option<String>,
#[serde(default)]
pub source_id: Option<String>,
}

// ---------------------------------------------------------------------------
Expand All @@ -40,6 +46,10 @@ pub async fn notify_track_started(
state: State<'_, AppState>,
payload: TrackStartedPayload,
) -> Result<(), SoneError> {
let source = match (payload.source_type, payload.source_id) {
(Some(t), Some(id)) => Some((t, id)),
_ => None,
};
let track = ScrobbleTrack {
artist: payload.artist,
track: payload.title,
Expand All @@ -55,30 +65,38 @@ pub async fn notify_track_started(
artist_primary: payload.artist_primary,
artist_mbids: Vec::new(),
};
state
.tidal_reporter
.on_track_started(payload.track_id, payload.duration_secs, source)
.await;
state.scrobble_manager.on_track_started(track).await;
Ok(())
}

#[tauri::command(rename_all = "camelCase")]
pub async fn notify_track_paused(state: State<'_, AppState>) -> Result<(), SoneError> {
state.tidal_reporter.on_pause().await;
state.scrobble_manager.on_pause().await;
Ok(())
}

#[tauri::command(rename_all = "camelCase")]
pub async fn notify_track_resumed(state: State<'_, AppState>) -> Result<(), SoneError> {
state.tidal_reporter.on_resume().await;
state.scrobble_manager.on_resume().await;
Ok(())
}

#[tauri::command(rename_all = "camelCase")]
pub async fn notify_track_seeked(state: State<'_, AppState>) -> Result<(), SoneError> {
state.tidal_reporter.on_seek().await;
state.scrobble_manager.on_seek().await;
Ok(())
}

#[tauri::command(rename_all = "camelCase")]
pub async fn notify_track_stopped(state: State<'_, AppState>) -> Result<(), SoneError> {
state.tidal_reporter.on_track_stopped().await;
state.scrobble_manager.on_track_stopped().await;
Ok(())
}
Expand Down
24 changes: 23 additions & 1 deletion src-tauri/src/commands/utility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,24 @@ pub fn set_discord_rpc(state: State<'_, AppState>, enabled: bool) -> Result<(),
Ok(())
}

#[tauri::command]
pub fn get_report_plays(state: State<'_, AppState>) -> bool {
state.load_settings().map(|s| s.report_plays).unwrap_or(false)
}

#[tauri::command]
pub async fn set_report_plays(state: State<'_, AppState>, enabled: bool) -> Result<(), SoneError> {
state.tidal_reporter.set_enabled(enabled);
if enabled {
// Flush any offline backlog now that reporting is on.
state.tidal_reporter.drain_queue().await;
}
let mut settings = state.load_settings().unwrap_or_default();
settings.report_plays = enabled;
state.save_settings(&settings)?;
Ok(())
}

#[tauri::command]
pub fn get_discord_status_text(state: State<'_, AppState>) -> String {
state
Expand Down Expand Up @@ -363,7 +381,11 @@ pub async fn set_proxy_settings(
let client = state.tidal_client.lock().await;
client.raw_client().clone()
};
state.scrobble_manager.update_http_client(new_client).await;
state
.scrobble_manager
.update_http_client(new_client.clone())
.await;
state.tidal_reporter.update_http_client(new_client);

// Save to disk
let mut app_settings = state.load_settings().unwrap_or_default();
Expand Down
31 changes: 25 additions & 6 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod pipeline_probe;
#[cfg(target_os = "linux")]
mod tray;
mod tidal_api;
mod tidal_report;
pub mod mcp;
pub mod overlay;

Expand Down Expand Up @@ -175,6 +176,9 @@ pub struct Settings {
pub overlay_port: u16,
#[serde(default = "defaults::overlay_host")]
pub overlay_host: String,
/// Report plays to TIDAL (Recently Played). Opt-in, off by default.
#[serde(default)]
pub report_plays: bool,
}

impl Default for Settings {
Expand Down Expand Up @@ -206,6 +210,7 @@ impl Default for Settings {
overlay_enabled: false,
overlay_port: 5578,
overlay_host: "127.0.0.1".to_string(),
report_plays: false,
}
}
}
Expand Down Expand Up @@ -236,6 +241,7 @@ pub struct AppState {
#[cfg(target_os = "linux")]
pub mpris: mpris::MprisHandle,
pub scrobble_manager: scrobble::ScrobbleManager,
pub tidal_reporter: tidal_report::TidalReporter,
pub discord: discord::DiscordHandle,
pub idle_inhibitor: Mutex<idle_inhibit::IdleInhibitor>,
pub mcp_state: crate::mcp::McpStateRef,
Expand Down Expand Up @@ -345,10 +351,19 @@ impl AppState {
.unwrap()
});
let scrobble_manager = scrobble::ScrobbleManager::new(
app_handle.clone(),
crypto.clone(),
&config_dir,
scrobble_http_client.clone(),
);

let report_plays = saved.as_ref().map(|s| s.report_plays).unwrap_or(false);
let tidal_reporter = tidal_report::TidalReporter::new(
app_handle.clone(),
crypto.clone(),
&config_dir,
scrobble_http_client,
report_plays,
);

let discord_rpc_enabled = saved.as_ref().map(|s| s.discord_rpc).unwrap_or(true);
Expand Down Expand Up @@ -399,6 +414,7 @@ impl AppState {
#[cfg(target_os = "linux")]
mpris: mpris::MprisHandle::new(app_handle),
scrobble_manager,
tidal_reporter,
discord: discord_handle,
idle_inhibitor: Mutex::new(idle_inhibit::IdleInhibitor::new()),
mcp_state: crate::mcp::new_state(),
Expand Down Expand Up @@ -631,8 +647,9 @@ pub fn run() {
}
}

// Drain retry queue in background
// Drain retry queues in background
state.scrobble_manager.drain_queue().await;
state.tidal_reporter.drain_queue().await;
});
}

Expand All @@ -644,6 +661,7 @@ pub fn run() {
tauri::async_runtime::spawn(async move {
let state = handle.state::<AppState>();
state.scrobble_manager.try_scrobble_finished().await;
state.tidal_reporter.try_finish().await;
});
});
}
Expand Down Expand Up @@ -677,11 +695,9 @@ pub fn run() {

let handle = handle.clone();
tauri::async_runtime::spawn(async move {
handle
.state::<AppState>()
.scrobble_manager
.try_scrobble_finished()
.await;
let state = handle.state::<AppState>();
state.scrobble_manager.try_scrobble_finished().await;
state.tidal_reporter.try_finish().await;
});
});
}
Expand Down Expand Up @@ -972,6 +988,8 @@ pub fn run() {
commands::utility::list_audio_devices,
commands::utility::get_discord_rpc,
commands::utility::set_discord_rpc,
commands::utility::get_report_plays,
commands::utility::set_report_plays,
commands::utility::get_discord_status_text,
commands::utility::set_discord_status_text,
commands::utility::get_proxy_settings,
Expand Down Expand Up @@ -1005,6 +1023,7 @@ pub fn run() {
tauri::async_runtime::block_on(async {
state.idle_inhibitor.lock().await.uninhibit().await;
state.scrobble_manager.flush().await;
state.tidal_reporter.flush().await;
});
}
});
Expand Down
Loading