feat(discover): add get_dynamic_suggest_full carrying track_to_analysed
Tauri's DailyQ/WeeklyQ /dynamic/suggest seeds a track_to_analysed payload
(~9 resolved {track_id,artist_id,genre_id,label_id} tuples) for the PRIMARY
mix algorithm; the empty-analysis call is only its fallback. The Slint port
only ever sent the empty-analysis path. Add a TrackToAnalyse model
(qbz-models) and a get_dynamic_suggest_full method on the qobuz client and
qbz-core that carries the payload; the existing get_dynamic_suggest now
delegates with an empty analysis (behaviour unchanged). Plumbing only — the
mix.rs build of the analysis tuples + the two-call fallback lands next (a2).
cc
This commit is contained in:
4 files changed
+47
-3
No files matched your search
@@ -12,8 +12,8 @@ use qbz_models::{
|
|||||||
LabelListPage, LabelPageData, LabelStoryResponse, PageArtistResponse,
|
LabelListPage, LabelPageData, LabelStoryResponse, PageArtistResponse,
|
||||||
MostPopularItem, Playlist, PlaylistDuplicateResult, PlaylistTag, Quality, QueueState,
|
MostPopularItem, Playlist, PlaylistDuplicateResult, PlaylistTag, Quality, QueueState,
|
||||||
QueueTrack, ReleasesGridResponse,
|
QueueTrack, ReleasesGridResponse,
|
||||||
RepeatMode, SearchAllResults, SearchResultsPage, StreamUrl, Track, TracksContainer,
|
RepeatMode, SearchAllResults, SearchResultsPage, StreamUrl, Track, TrackToAnalyse,
|
||||||
UserSession,
|
TracksContainer, UserSession,
|
||||||
};
|
};
|
||||||
use qbz_integrations::musicbrainz::cache::MusicBrainzCache;
|
use qbz_integrations::musicbrainz::cache::MusicBrainzCache;
|
||||||
use qbz_integrations::musicbrainz::genre::{extract_affinity_seeds, genre_summary, is_broad_genre};
|
use qbz_integrations::musicbrainz::genre::{extract_affinity_seeds, genre_summary, is_broad_genre};
|
||||||
@@ -1319,6 +1319,22 @@ impl<A: FrontendAdapter + Send + Sync + 'static> QbzCore<A> {
|
|||||||
.map_err(CoreError::Api)
|
.map_err(CoreError::Api)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dynamic mix suggestions with the `track_to_analysed` payload — the
|
||||||
|
/// PRIMARY DailyQ/WeeklyQ path (see `QobuzClient::get_dynamic_suggest_full`).
|
||||||
|
pub async fn get_dynamic_suggest_full(
|
||||||
|
&self,
|
||||||
|
listened_track_ids: &[u64],
|
||||||
|
tracks_to_analyse: &[TrackToAnalyse],
|
||||||
|
limit: u32,
|
||||||
|
) -> Result<Vec<Track>, CoreError> {
|
||||||
|
let client = self.client.read().await;
|
||||||
|
let client = client.as_ref().ok_or(CoreError::NotInitialized)?;
|
||||||
|
client
|
||||||
|
.get_dynamic_suggest_full(listened_track_ids, tracks_to_analyse, limit)
|
||||||
|
.await
|
||||||
|
.map_err(CoreError::Api)
|
||||||
|
}
|
||||||
|
|
||||||
/// Smart artist radio via the local `qbz-radio` pool builder — a
|
/// Smart artist radio via the local `qbz-radio` pool builder — a
|
||||||
/// richer alternative to the Qobuz `/radio/artist` endpoint. Builds
|
/// richer alternative to the Qobuz `/radio/artist` endpoint. Builds
|
||||||
/// a session pool (seed tracks + similar artists + second-degree)
|
/// a session pool (seed tracks + similar artists + second-degree)
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ pub use types::{
|
|||||||
StreamUrl,
|
StreamUrl,
|
||||||
Track,
|
Track,
|
||||||
TrackFileUrl,
|
TrackFileUrl,
|
||||||
|
TrackToAnalyse,
|
||||||
TracksContainer,
|
TracksContainer,
|
||||||
UserSession,
|
UserSession,
|
||||||
};
|
};
|
||||||
@@ -9,6 +9,19 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
// ============ Dynamic-suggest (DailyQ/WeeklyQ) ============
|
||||||
|
|
||||||
|
/// A seed track resolved for the `/dynamic/suggest` `track_to_analysed`
|
||||||
|
/// payload (DailyQ / WeeklyQ). Field names match the Qobuz wire shape
|
||||||
|
/// exactly; `0` marks an unknown id (mirrors Tauri's `?? 0`).
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TrackToAnalyse {
|
||||||
|
pub track_id: u64,
|
||||||
|
pub artist_id: u64,
|
||||||
|
pub genre_id: u64,
|
||||||
|
pub label_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
// ============ Quality Types ============
|
// ============ Quality Types ============
|
||||||
|
|
||||||
/// Audio quality format IDs (matches Qobuz API format IDs)
|
/// Audio quality format IDs (matches Qobuz API format IDs)
|
||||||
|
|||||||
@@ -985,12 +985,26 @@ impl QobuzClient {
|
|||||||
&self,
|
&self,
|
||||||
listened_track_ids: &[u64],
|
listened_track_ids: &[u64],
|
||||||
limit: u32,
|
limit: u32,
|
||||||
|
) -> Result<Vec<Track>> {
|
||||||
|
self.get_dynamic_suggest_full(listened_track_ids, &[], limit)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like [`get_dynamic_suggest`] but carrying the `track_to_analysed`
|
||||||
|
/// payload — the PRIMARY DailyQ/WeeklyQ path. Tauri seeds this with up to 9
|
||||||
|
/// resolved `{track_id, artist_id, genre_id, label_id}` tuples and only
|
||||||
|
/// falls back to an empty analysis when a call returns zero items.
|
||||||
|
pub async fn get_dynamic_suggest_full(
|
||||||
|
&self,
|
||||||
|
listened_track_ids: &[u64],
|
||||||
|
tracks_to_analyse: &[TrackToAnalyse],
|
||||||
|
limit: u32,
|
||||||
) -> Result<Vec<Track>> {
|
) -> Result<Vec<Track>> {
|
||||||
let url = endpoints::build_url(paths::DYNAMIC_SUGGEST);
|
let url = endpoints::build_url(paths::DYNAMIC_SUGGEST);
|
||||||
let body = serde_json::json!({
|
let body = serde_json::json!({
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"listened_tracks_ids": listened_track_ids,
|
"listened_tracks_ids": listened_track_ids,
|
||||||
"track_to_analysed": [],
|
"track_to_analysed": tracks_to_analyse,
|
||||||
});
|
});
|
||||||
let http_response = self
|
let http_response = self
|
||||||
.http()?
|
.http()?
|
||||||
|
|||||||
Reference in new issue
Block a user