metadata: per-track listeners, and an artist profile the search hero can ask for
CatalogTrack gains artist_id — Deezer sends it on every search row and it was simply unparsed — and rank, its 0-1,000,000 popularity figure. Together they let a listener count be resolved for the PERFORMING artist of each row rather than for the query's top artist, so a "panda bear" search shows Daft Punk's "Doin' it Right (feat. Panda Bear)" at its real 464,046. Resolution rides the existing deezer_linked_mbid -> lastfm_listener_ranks chain behind a new catalog_track_listeners command, deliberately kept separate from catalog_search: that one is typed against per keystroke and must never wait on MusicBrainz's 1 req/s gate. catalog_artist_profile backs the hero's middle third — MusicBrainz tags off the cached artist document plus one Last.fm artist.getinfo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f8f56f4398
commit
dc918c98ef
3 files changed
+285
No files matched your search
@@ -2533,6 +2533,46 @@ pub async fn catalog_artist_popular(
|
||||
.ok_or_else(|| "artist popular lookup failed".to_string())
|
||||
}
|
||||
|
||||
/// Genres and Last.fm totals for the artist a search is about — the search
|
||||
/// hero's stat line and tag chips. Deferred like every other supplement: it
|
||||
/// rides MusicBrainz's gate and must never hold up the results.
|
||||
#[tauri::command]
|
||||
pub async fn catalog_artist_profile(
|
||||
state: State<'_, LibraryState>,
|
||||
artist_id: u64,
|
||||
) -> Result<metadata::CatalogArtistProfile, String> {
|
||||
state.touch_catalog_activity();
|
||||
Ok(state.metadata.artist_profile(artist_id).await)
|
||||
}
|
||||
|
||||
/// One search-result row, reduced to what a listener lookup needs.
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TrackListenerQuery {
|
||||
/// The PERFORMING artist's Deezer id. `None` for a row the provider gave no
|
||||
/// artist id for, which simply gets no count — never a guess from the name.
|
||||
pub artist_id: Option<u64>,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
/// Last.fm listener counts for a set of search-result rows, answered in order.
|
||||
///
|
||||
/// Deferred on purpose: `catalog_search` is typed against per keystroke and must
|
||||
/// stay three Deezer calls, so the counts are asked for separately once the
|
||||
/// results are on screen. See `MetadataClient::track_listeners`.
|
||||
#[tauri::command]
|
||||
pub async fn catalog_track_listeners(
|
||||
state: State<'_, LibraryState>,
|
||||
tracks: Vec<TrackListenerQuery>,
|
||||
) -> Result<Vec<Option<u64>>, String> {
|
||||
state.touch_catalog_activity();
|
||||
let rows: Vec<(Option<u64>, String)> = tracks
|
||||
.into_iter()
|
||||
.map(|t| (t.artist_id, t.title))
|
||||
.collect();
|
||||
Ok(state.metadata.track_listeners(&rows).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn catalog_related(
|
||||
state: State<'_, LibraryState>,
|
||||
@@ -4217,6 +4257,21 @@ pub async fn waveform(
|
||||
result
|
||||
}
|
||||
|
||||
/// What the audio thread is doing right now.
|
||||
///
|
||||
/// The webview learns about playback only from events, so a RELOAD leaves it
|
||||
/// believing nothing is playing while audio carries on — the scrubber moved
|
||||
/// under the words "Nothing playing". This is the boot-time answer, the same
|
||||
/// shape of recovery `app_status` already provides for the Soulseek connection.
|
||||
#[tauri::command]
|
||||
pub fn player_state(players: State<'_, PlayerState>) -> crate::playback::PlaybackSnapshot {
|
||||
players
|
||||
.last_transport
|
||||
.lock()
|
||||
.map(|snapshot| snapshot.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Player transport: action ∈ pause | resume | stop, or seek/volume with value.
|
||||
#[tauri::command]
|
||||
pub fn player_control(
|
||||
|
||||
Reference in new issue
Block a user