feat: full artist release list via artist/getReleasesList
Instead of relying on the limited preview in artist/page, fire a separate artist/getReleasesList request per release type (album, epSingle, live, compilation) in parallel when loading an artist. Each result updates its section independently as it arrives, so the page populates progressively without a single large request. Also fixes the artist name in the status bar (was reading wrong field). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,9 @@ uint32_t qobuz_backend_viz_read(QobuzBackendOpaque *backend, float *buf, uint32_
|
|||||||
uint32_t qobuz_backend_viz_sample_rate(const QobuzBackendOpaque *backend);
|
uint32_t qobuz_backend_viz_sample_rate(const QobuzBackendOpaque *backend);
|
||||||
uint32_t qobuz_backend_viz_channels(const QobuzBackendOpaque *backend);
|
uint32_t qobuz_backend_viz_channels(const QobuzBackendOpaque *backend);
|
||||||
|
|
||||||
|
// Artist releases (full paginated list per release type)
|
||||||
|
void qobuz_backend_get_artist_releases(QobuzBackendOpaque *backend, int64_t artist_id, const char *release_type, uint32_t limit, uint32_t offset);
|
||||||
|
|
||||||
// Playlist management
|
// Playlist management
|
||||||
void qobuz_backend_create_playlist(QobuzBackendOpaque *backend, const char *name);
|
void qobuz_backend_create_playlist(QobuzBackendOpaque *backend, const char *name);
|
||||||
void qobuz_backend_delete_playlist(QobuzBackendOpaque *backend, int64_t playlist_id);
|
void qobuz_backend_delete_playlist(QobuzBackendOpaque *backend, int64_t playlist_id);
|
||||||
|
|||||||
@@ -267,6 +267,28 @@ impl QobuzClient {
|
|||||||
Self::check_response(resp).await
|
Self::check_response(resp).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_artist_releases_list(
|
||||||
|
&self,
|
||||||
|
artist_id: i64,
|
||||||
|
release_type: &str,
|
||||||
|
limit: u32,
|
||||||
|
offset: u32,
|
||||||
|
) -> Result<Value> {
|
||||||
|
let resp = self
|
||||||
|
.get_request("artist/getReleasesList")
|
||||||
|
.query(&[
|
||||||
|
("artist_id", artist_id.to_string()),
|
||||||
|
("release_type", release_type.to_string()),
|
||||||
|
("sort", "release_date".to_string()),
|
||||||
|
("order", "desc".to_string()),
|
||||||
|
("limit", limit.to_string()),
|
||||||
|
("offset", offset.to_string()),
|
||||||
|
])
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
Self::check_response(resp).await
|
||||||
|
}
|
||||||
|
|
||||||
// --- Search ---
|
// --- Search ---
|
||||||
|
|
||||||
pub async fn search(&self, query: &str, offset: u32, limit: u32) -> Result<SearchCatalogDto> {
|
pub async fn search(&self, query: &str, offset: u32, limit: u32) -> Result<SearchCatalogDto> {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ pub const EV_POSITION: c_int = 16;
|
|||||||
pub const EV_TRACK_URL_OK: c_int = 17;
|
pub const EV_TRACK_URL_OK: c_int = 17;
|
||||||
pub const EV_TRACK_URL_ERR: c_int = 18;
|
pub const EV_TRACK_URL_ERR: c_int = 18;
|
||||||
pub const EV_GENERIC_ERR: c_int = 19;
|
pub const EV_GENERIC_ERR: c_int = 19;
|
||||||
|
pub const EV_ARTIST_RELEASES_OK: c_int = 24;
|
||||||
|
|
||||||
// ---------- Callback ----------
|
// ---------- Callback ----------
|
||||||
|
|
||||||
@@ -256,6 +257,38 @@ pub unsafe extern "C" fn qobuz_backend_get_artist(ptr: *mut Backend, artist_id:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Artist releases ----------
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn qobuz_backend_get_artist_releases(
|
||||||
|
ptr: *mut Backend,
|
||||||
|
artist_id: i64,
|
||||||
|
release_type: *const c_char,
|
||||||
|
limit: u32,
|
||||||
|
offset: u32,
|
||||||
|
) {
|
||||||
|
let inner = &(*ptr).0;
|
||||||
|
let client = inner.client.clone();
|
||||||
|
let cb = inner.cb; let ud = inner.ud;
|
||||||
|
let rtype = CStr::from_ptr(release_type).to_string_lossy().into_owned();
|
||||||
|
|
||||||
|
spawn(inner, async move {
|
||||||
|
let result = client.lock().await
|
||||||
|
.get_artist_releases_list(artist_id, &rtype, limit, offset)
|
||||||
|
.await;
|
||||||
|
let (ev, json) = match result {
|
||||||
|
Ok(r) => {
|
||||||
|
// Wrap with the release_type so Qt can route to the right section
|
||||||
|
let mut obj = r.as_object().cloned().unwrap_or_default();
|
||||||
|
obj.insert("release_type".to_string(), serde_json::Value::String(rtype));
|
||||||
|
(EV_ARTIST_RELEASES_OK, serde_json::to_string(&obj).unwrap_or_default())
|
||||||
|
}
|
||||||
|
Err(e) => (EV_GENERIC_ERR, err_json(&e.to_string())),
|
||||||
|
};
|
||||||
|
call_cb(cb, ud, ev, &json);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Playlist ----------
|
// ---------- Playlist ----------
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ void QobuzBackend::getArtist(qint64 artistId)
|
|||||||
qobuz_backend_get_artist(m_backend, artistId);
|
qobuz_backend_get_artist(m_backend, artistId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QobuzBackend::getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit, quint32 offset)
|
||||||
|
{
|
||||||
|
qobuz_backend_get_artist_releases(m_backend, artistId,
|
||||||
|
releaseType.toUtf8().constData(), limit, offset);
|
||||||
|
}
|
||||||
|
|
||||||
void QobuzBackend::getPlaylist(qint64 playlistId, quint32 offset, quint32 limit)
|
void QobuzBackend::getPlaylist(qint64 playlistId, quint32 offset, quint32 limit)
|
||||||
{
|
{
|
||||||
qobuz_backend_get_playlist(m_backend, playlistId, offset, limit);
|
qobuz_backend_get_playlist(m_backend, playlistId, offset, limit);
|
||||||
@@ -242,6 +248,9 @@ void QobuzBackend::onEvent(int eventType, const QString &json)
|
|||||||
case EV_ARTIST_OK:
|
case EV_ARTIST_OK:
|
||||||
emit artistLoaded(obj);
|
emit artistLoaded(obj);
|
||||||
break;
|
break;
|
||||||
|
case 24: // EV_ARTIST_RELEASES_OK
|
||||||
|
emit artistReleasesLoaded(obj["release_type"].toString(), obj["items"].toArray());
|
||||||
|
break;
|
||||||
case EV_ARTIST_ERR:
|
case EV_ARTIST_ERR:
|
||||||
emit error(obj["error"].toString());
|
emit error(obj["error"].toString());
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ public:
|
|||||||
void search(const QString &query, quint32 offset = 0, quint32 limit = 20);
|
void search(const QString &query, quint32 offset = 0, quint32 limit = 20);
|
||||||
void getAlbum(const QString &albumId);
|
void getAlbum(const QString &albumId);
|
||||||
void getArtist(qint64 artistId);
|
void getArtist(qint64 artistId);
|
||||||
|
void getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit = 500, quint32 offset = 0);
|
||||||
void getPlaylist(qint64 playlistId, quint32 offset = 0, quint32 limit = 500);
|
void getPlaylist(qint64 playlistId, quint32 offset = 0, quint32 limit = 500);
|
||||||
|
|
||||||
// --- favorites ---
|
// --- favorites ---
|
||||||
@@ -83,6 +85,7 @@ signals:
|
|||||||
void searchResult(const QJsonObject &result);
|
void searchResult(const QJsonObject &result);
|
||||||
void albumLoaded(const QJsonObject &album);
|
void albumLoaded(const QJsonObject &album);
|
||||||
void artistLoaded(const QJsonObject &artist);
|
void artistLoaded(const QJsonObject &artist);
|
||||||
|
void artistReleasesLoaded(const QString &releaseType, const QJsonArray &items);
|
||||||
void playlistLoaded(const QJsonObject &playlist);
|
void playlistLoaded(const QJsonObject &playlist);
|
||||||
void playlistCreated(const QJsonObject &playlist);
|
void playlistCreated(const QJsonObject &playlist);
|
||||||
void playlistDeleted(const QJsonObject &result);
|
void playlistDeleted(const QJsonObject &result);
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent)
|
|||||||
connect(m_backend, &QobuzBackend::favArtistsLoaded, this, &MainWindow::onFavArtistsLoaded);
|
connect(m_backend, &QobuzBackend::favArtistsLoaded, this, &MainWindow::onFavArtistsLoaded);
|
||||||
connect(m_backend, &QobuzBackend::albumLoaded, this, &MainWindow::onAlbumLoaded);
|
connect(m_backend, &QobuzBackend::albumLoaded, this, &MainWindow::onAlbumLoaded);
|
||||||
connect(m_backend, &QobuzBackend::artistLoaded, this, &MainWindow::onArtistLoaded);
|
connect(m_backend, &QobuzBackend::artistLoaded, this, &MainWindow::onArtistLoaded);
|
||||||
|
connect(m_backend, &QobuzBackend::artistReleasesLoaded,
|
||||||
|
m_content, &MainContent::updateArtistReleases);
|
||||||
connect(m_backend, &QobuzBackend::playlistLoaded, this, &MainWindow::onPlaylistLoaded);
|
connect(m_backend, &QobuzBackend::playlistLoaded, this, &MainWindow::onPlaylistLoaded);
|
||||||
connect(m_backend, &QobuzBackend::playlistCreated, this, &MainWindow::onPlaylistCreated);
|
connect(m_backend, &QobuzBackend::playlistCreated, this, &MainWindow::onPlaylistCreated);
|
||||||
connect(m_backend, &QobuzBackend::playlistDeleted, this, [this](const QJsonObject &) {
|
connect(m_backend, &QobuzBackend::playlistDeleted, this, [this](const QJsonObject &) {
|
||||||
@@ -334,7 +336,7 @@ void MainWindow::onArtistLoaded(const QJsonObject &artist)
|
|||||||
{
|
{
|
||||||
m_content->showArtist(artist);
|
m_content->showArtist(artist);
|
||||||
statusBar()->showMessage(
|
statusBar()->showMessage(
|
||||||
tr("Artist: %1").arg(artist["name"].toString()), 4000);
|
tr("Artist: %1").arg(artist["name"].toObject()["display"].toString()), 4000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onPlaylistLoaded(const QJsonObject &playlist)
|
void MainWindow::onPlaylistLoaded(const QJsonObject &playlist)
|
||||||
@@ -358,6 +360,9 @@ void MainWindow::onSearchAlbumSelected(const QString &albumId)
|
|||||||
void MainWindow::onSearchArtistSelected(qint64 artistId)
|
void MainWindow::onSearchArtistSelected(qint64 artistId)
|
||||||
{
|
{
|
||||||
m_backend->getArtist(artistId);
|
m_backend->getArtist(artistId);
|
||||||
|
// Fire release-type requests in parallel — each updates its section when it arrives
|
||||||
|
for (const char *type : {"album", "epSingle", "live", "compilation"})
|
||||||
|
m_backend->getArtistReleases(artistId, QString::fromLatin1(type));
|
||||||
statusBar()->showMessage(tr("Loading artist…"));
|
statusBar()->showMessage(tr("Loading artist…"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,32 +195,29 @@ void ArtistView::setArtist(const QJsonObject &artist)
|
|||||||
m_topTracks->loadTracks(topTracks);
|
m_topTracks->loadTracks(topTracks);
|
||||||
m_topTracksSection->setVisible(!topTracks.isEmpty());
|
m_topTracksSection->setVisible(!topTracks.isEmpty());
|
||||||
|
|
||||||
// releases is an array of {type, has_more, items[]}
|
// Release sections are populated asynchronously via setReleases().
|
||||||
const QJsonArray releases = artist["releases"].toArray();
|
// Clear them now so stale data from a previous artist isn't shown.
|
||||||
QJsonArray albums, eps, live, compilations;
|
m_secAlbums->setAlbums({});
|
||||||
for (const QJsonValue &rv : releases) {
|
m_secEps->setAlbums({});
|
||||||
const QJsonObject rg = rv.toObject();
|
m_secLive->setAlbums({});
|
||||||
const QString type = rg["type"].toString();
|
m_secCompilations->setAlbums({});
|
||||||
const QJsonArray items = rg["items"].toArray();
|
|
||||||
if (type == QStringLiteral("album"))
|
|
||||||
albums = items;
|
|
||||||
else if (type == QStringLiteral("epSingle"))
|
|
||||||
eps = items;
|
|
||||||
else if (type == QStringLiteral("live"))
|
|
||||||
live = items;
|
|
||||||
else if (type == QStringLiteral("compilation"))
|
|
||||||
compilations = items;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_secAlbums->setAlbums(albums);
|
|
||||||
m_secEps->setAlbums(eps);
|
|
||||||
m_secLive->setAlbums(live);
|
|
||||||
m_secCompilations->setAlbums(compilations);
|
|
||||||
m_secOther->setAlbums({});
|
m_secOther->setAlbums({});
|
||||||
|
m_secAlbums->setVisible(false);
|
||||||
m_secAlbums->setVisible(!m_secAlbums->isEmpty());
|
m_secEps->setVisible(false);
|
||||||
m_secEps->setVisible(!m_secEps->isEmpty());
|
m_secLive->setVisible(false);
|
||||||
m_secLive->setVisible(!m_secLive->isEmpty());
|
m_secCompilations->setVisible(false);
|
||||||
m_secCompilations->setVisible(!m_secCompilations->isEmpty());
|
|
||||||
m_secOther->setVisible(false);
|
m_secOther->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ArtistView::setReleases(const QString &releaseType, const QJsonArray &items)
|
||||||
|
{
|
||||||
|
ArtistSection *sec = nullptr;
|
||||||
|
if (releaseType == QStringLiteral("album")) sec = m_secAlbums;
|
||||||
|
else if (releaseType == QStringLiteral("epSingle")) sec = m_secEps;
|
||||||
|
else if (releaseType == QStringLiteral("live")) sec = m_secLive;
|
||||||
|
else if (releaseType == QStringLiteral("compilation")) sec = m_secCompilations;
|
||||||
|
else sec = m_secOther;
|
||||||
|
|
||||||
|
sec->setAlbums(items);
|
||||||
|
sec->setVisible(!sec->isEmpty());
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public:
|
|||||||
explicit ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent = nullptr);
|
explicit ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setArtist(const QJsonObject &artist);
|
void setArtist(const QJsonObject &artist);
|
||||||
|
/// Update a specific release section when getReleasesList responds.
|
||||||
|
void setReleases(const QString &releaseType, const QJsonArray &items);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void albumSelected(const QString &albumId);
|
void albumSelected(const QString &albumId);
|
||||||
|
|||||||
@@ -107,3 +107,8 @@ void MainContent::showArtist(const QJsonObject &artist)
|
|||||||
m_artistView->setArtist(artist);
|
m_artistView->setArtist(artist);
|
||||||
m_stack->setCurrentIndex(4);
|
m_stack->setCurrentIndex(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainContent::updateArtistReleases(const QString &releaseType, const QJsonArray &items)
|
||||||
|
{
|
||||||
|
m_artistView->setReleases(releaseType, items);
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public:
|
|||||||
void showFavAlbums(const QJsonObject &result);
|
void showFavAlbums(const QJsonObject &result);
|
||||||
void showFavArtists(const QJsonObject &result);
|
void showFavArtists(const QJsonObject &result);
|
||||||
void showArtist(const QJsonObject &artist);
|
void showArtist(const QJsonObject &artist);
|
||||||
|
void updateArtistReleases(const QString &releaseType, const QJsonArray &items);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void albumRequested(const QString &albumId);
|
void albumRequested(const QString &albumId);
|
||||||
|
|||||||
Reference in New Issue
Block a user