feat: add seamless lazy loading for genre and playlist views
Some checks failed
Build for Windows / build-windows (push) Has been cancelled
Some checks failed
Build for Windows / build-windows (push) Has been cancelled
Introduce paged loading with early prefetch for genre albums/playlists and playlist tracks, while preserving full-data behavior for deep shuffle and playlist play-all actions.
This commit is contained in:
@@ -67,6 +67,7 @@ void qobuz_backend_get_dynamic_suggestions(QobuzBackendOpaque *backend, const ch
|
||||
void qobuz_backend_get_album(QobuzBackendOpaque *backend, const char *album_id);
|
||||
void qobuz_backend_get_artist(QobuzBackendOpaque *backend, int64_t artist_id);
|
||||
void qobuz_backend_get_playlist(QobuzBackendOpaque *backend, int64_t playlist_id, uint32_t offset, uint32_t limit);
|
||||
void qobuz_backend_get_playlist_all(QobuzBackendOpaque *backend, int64_t playlist_id);
|
||||
|
||||
// Favorites
|
||||
void qobuz_backend_get_fav_tracks(QobuzBackendOpaque *backend, uint32_t offset, uint32_t limit);
|
||||
|
||||
@@ -639,6 +639,51 @@ impl QobuzClient {
|
||||
Ok(serde_json::from_value(body)?)
|
||||
}
|
||||
|
||||
pub async fn get_playlist_all(&self, playlist_id: i64) -> Result<PlaylistDto> {
|
||||
const PAGE_LIMIT: u32 = 500;
|
||||
|
||||
let mut playlist = self.get_playlist(playlist_id, 0, PAGE_LIMIT).await?;
|
||||
|
||||
let mut all_items = playlist
|
||||
.tracks
|
||||
.as_ref()
|
||||
.and_then(|t| t.items.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut total = playlist
|
||||
.tracks
|
||||
.as_ref()
|
||||
.and_then(|t| t.total)
|
||||
.unwrap_or(all_items.len() as i32);
|
||||
if total < all_items.len() as i32 {
|
||||
total = all_items.len() as i32;
|
||||
}
|
||||
|
||||
let mut offset = all_items.len() as u32;
|
||||
while (offset as i32) < total {
|
||||
let page = self.get_playlist(playlist_id, offset, PAGE_LIMIT).await?;
|
||||
let mut page_items = page
|
||||
.tracks
|
||||
.as_ref()
|
||||
.and_then(|t| t.items.clone())
|
||||
.unwrap_or_default();
|
||||
if page_items.is_empty() {
|
||||
break;
|
||||
}
|
||||
all_items.append(&mut page_items);
|
||||
offset = all_items.len() as u32;
|
||||
}
|
||||
|
||||
if let Some(tracks) = playlist.tracks.as_mut() {
|
||||
tracks.items = Some(all_items);
|
||||
tracks.total = Some(total);
|
||||
tracks.offset = Some(0);
|
||||
tracks.limit = Some(PAGE_LIMIT as i32);
|
||||
}
|
||||
|
||||
Ok(playlist)
|
||||
}
|
||||
|
||||
/// Fetch all favorite IDs (tracks, albums, artists) in one call.
|
||||
async fn get_fav_ids(&self) -> Result<FavIdsDto> {
|
||||
let resp = self
|
||||
|
||||
@@ -514,6 +514,8 @@ pub unsafe extern "C" fn qobuz_backend_get_featured_albums(
|
||||
"total": total,
|
||||
"type": kind_str,
|
||||
"genre_ids": genre_ids_str,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
});
|
||||
call_cb(
|
||||
cb,
|
||||
@@ -557,6 +559,8 @@ pub unsafe extern "C" fn qobuz_backend_get_featured_playlists(
|
||||
"total": total,
|
||||
"type": kind_str,
|
||||
"genre_ids": genre_ids_str,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
});
|
||||
call_cb(
|
||||
cb,
|
||||
@@ -600,6 +604,8 @@ pub unsafe extern "C" fn qobuz_backend_discover_playlists(
|
||||
"total": total,
|
||||
"genre_ids": genre_ids_str,
|
||||
"tags": tags_str,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
});
|
||||
call_cb(
|
||||
cb,
|
||||
@@ -640,6 +646,8 @@ pub unsafe extern "C" fn qobuz_backend_search_playlists(
|
||||
"items": items,
|
||||
"total": total,
|
||||
"query": query_str,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
});
|
||||
call_cb(
|
||||
cb,
|
||||
@@ -684,6 +692,28 @@ pub unsafe extern "C" fn qobuz_backend_get_playlist(
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn qobuz_backend_get_playlist_all(ptr: *mut Backend, playlist_id: i64) {
|
||||
let inner = &(*ptr).0;
|
||||
let client = inner.client.clone();
|
||||
let cb = inner.cb;
|
||||
let ud = inner.ud;
|
||||
spawn(inner, async move {
|
||||
let result = client.lock().await.get_playlist_all(playlist_id).await;
|
||||
let (ev, json) = match result {
|
||||
Ok(r) => {
|
||||
let mut v = serde_json::to_value(&r).unwrap_or_default();
|
||||
if let serde_json::Value::Object(ref mut obj) = v {
|
||||
obj.insert("full_load".to_string(), serde_json::Value::Bool(true));
|
||||
}
|
||||
(EV_PLAYLIST_OK, serde_json::to_string(&v).unwrap_or_default())
|
||||
}
|
||||
Err(e) => (EV_PLAYLIST_ERR, err_json(&e.to_string())),
|
||||
};
|
||||
call_cb(cb, ud, ev, &json);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Favorites ----------
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
Reference in New Issue
Block a user