- Adds extra=topTracks to artist/page API request - Embeds a List::Tracks widget at the top of ArtistView showing the artist's most popular tracks, with Play and Shuffle buttons - Bubbles playTrackRequested through MainContent up to MainWindow - Also adds the viz PCM ring buffer FFI infrastructure (for future spectrum widget) to the Rust backend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
307 lines
7.5 KiB
C++
307 lines
7.5 KiB
C++
#include "qobuzbackend.hpp"
|
|
|
|
#include <QJsonDocument>
|
|
#include <QJsonArray>
|
|
#include <QMetaObject>
|
|
|
|
QobuzBackend::QobuzBackend(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
m_backend = qobuz_backend_new(&QobuzBackend::eventTrampoline, this);
|
|
if (!m_backend) {
|
|
qCritical("Failed to initialize Qobuz backend");
|
|
return;
|
|
}
|
|
|
|
m_positionTimer = new QTimer(this);
|
|
m_positionTimer->setInterval(50);
|
|
connect(m_positionTimer, &QTimer::timeout, this, &QobuzBackend::onPositionTick);
|
|
m_positionTimer->start();
|
|
}
|
|
|
|
QobuzBackend::~QobuzBackend()
|
|
{
|
|
if (m_backend) {
|
|
qobuz_backend_free(m_backend);
|
|
m_backend = nullptr;
|
|
}
|
|
}
|
|
|
|
// ---- auth ----
|
|
|
|
void QobuzBackend::login(const QString &email, const QString &password)
|
|
{
|
|
qobuz_backend_login(m_backend, email.toUtf8().constData(), password.toUtf8().constData());
|
|
}
|
|
|
|
void QobuzBackend::setToken(const QString &token)
|
|
{
|
|
qobuz_backend_set_token(m_backend, token.toUtf8().constData());
|
|
}
|
|
|
|
void QobuzBackend::getUser()
|
|
{
|
|
qobuz_backend_get_user(m_backend);
|
|
}
|
|
|
|
// ---- catalog ----
|
|
|
|
void QobuzBackend::search(const QString &query, quint32 offset, quint32 limit)
|
|
{
|
|
qobuz_backend_search(m_backend, query.toUtf8().constData(), offset, limit);
|
|
}
|
|
|
|
void QobuzBackend::getAlbum(const QString &albumId)
|
|
{
|
|
qobuz_backend_get_album(m_backend, albumId.toUtf8().constData());
|
|
}
|
|
|
|
void QobuzBackend::getArtist(qint64 artistId)
|
|
{
|
|
qobuz_backend_get_artist(m_backend, artistId);
|
|
}
|
|
|
|
void QobuzBackend::getPlaylist(qint64 playlistId, quint32 offset, quint32 limit)
|
|
{
|
|
qobuz_backend_get_playlist(m_backend, playlistId, offset, limit);
|
|
}
|
|
|
|
// ---- favorites ----
|
|
|
|
void QobuzBackend::getFavTracks(quint32 offset, quint32 limit)
|
|
{
|
|
qobuz_backend_get_fav_tracks(m_backend, offset, limit);
|
|
}
|
|
|
|
void QobuzBackend::getFavAlbums(quint32 offset, quint32 limit)
|
|
{
|
|
qobuz_backend_get_fav_albums(m_backend, offset, limit);
|
|
}
|
|
|
|
void QobuzBackend::getFavArtists(quint32 offset, quint32 limit)
|
|
{
|
|
qobuz_backend_get_fav_artists(m_backend, offset, limit);
|
|
}
|
|
|
|
void QobuzBackend::getUserPlaylists(quint32 offset, quint32 limit)
|
|
{
|
|
qobuz_backend_get_user_playlists(m_backend, offset, limit);
|
|
}
|
|
|
|
// ---- playback options ----
|
|
|
|
void QobuzBackend::setReplayGain(bool enabled)
|
|
{
|
|
qobuz_backend_set_replaygain(m_backend, enabled);
|
|
}
|
|
|
|
void QobuzBackend::setGapless(bool enabled)
|
|
{
|
|
qobuz_backend_set_gapless(m_backend, enabled);
|
|
}
|
|
|
|
void QobuzBackend::prefetchTrack(qint64 trackId, int formatId)
|
|
{
|
|
qobuz_backend_prefetch_track(m_backend, trackId, formatId);
|
|
}
|
|
|
|
// ---- playlist management ----
|
|
|
|
void QobuzBackend::createPlaylist(const QString &name)
|
|
{
|
|
qobuz_backend_create_playlist(m_backend, name.toUtf8().constData());
|
|
}
|
|
|
|
void QobuzBackend::deletePlaylist(qint64 playlistId)
|
|
{
|
|
qobuz_backend_delete_playlist(m_backend, playlistId);
|
|
}
|
|
|
|
void QobuzBackend::addTrackToPlaylist(qint64 playlistId, qint64 trackId)
|
|
{
|
|
qobuz_backend_add_track_to_playlist(m_backend, playlistId, trackId);
|
|
}
|
|
|
|
void QobuzBackend::deleteTrackFromPlaylist(qint64 playlistId, qint64 playlistTrackId)
|
|
{
|
|
qobuz_backend_delete_track_from_playlist(m_backend, playlistId, playlistTrackId);
|
|
}
|
|
|
|
// ---- fav modification ----
|
|
|
|
void QobuzBackend::addFavTrack(qint64 trackId)
|
|
{
|
|
qobuz_backend_add_fav_track(m_backend, trackId);
|
|
}
|
|
|
|
void QobuzBackend::removeFavTrack(qint64 trackId)
|
|
{
|
|
qobuz_backend_remove_fav_track(m_backend, trackId);
|
|
}
|
|
|
|
void QobuzBackend::addFavAlbum(const QString &albumId)
|
|
{
|
|
qobuz_backend_add_fav_album(m_backend, albumId.toUtf8().constData());
|
|
}
|
|
|
|
void QobuzBackend::removeFavAlbum(const QString &albumId)
|
|
{
|
|
qobuz_backend_remove_fav_album(m_backend, albumId.toUtf8().constData());
|
|
}
|
|
|
|
// ---- playback ----
|
|
|
|
void QobuzBackend::playTrack(qint64 trackId, int formatId)
|
|
{
|
|
qobuz_backend_play_track(m_backend, trackId, formatId);
|
|
}
|
|
|
|
void QobuzBackend::pause()
|
|
{
|
|
qobuz_backend_pause(m_backend);
|
|
}
|
|
|
|
void QobuzBackend::resume()
|
|
{
|
|
qobuz_backend_resume(m_backend);
|
|
}
|
|
|
|
void QobuzBackend::stop()
|
|
{
|
|
qobuz_backend_stop(m_backend);
|
|
}
|
|
|
|
void QobuzBackend::setVolume(int volume)
|
|
{
|
|
qobuz_backend_set_volume(m_backend, static_cast<quint8>(qBound(0, volume, 100)));
|
|
}
|
|
|
|
void QobuzBackend::seek(quint64 positionSecs)
|
|
{
|
|
qobuz_backend_seek(m_backend, positionSecs);
|
|
}
|
|
|
|
quint64 QobuzBackend::position() const { return qobuz_backend_get_position(m_backend); }
|
|
quint64 QobuzBackend::duration() const { return qobuz_backend_get_duration(m_backend); }
|
|
int QobuzBackend::volume() const { return qobuz_backend_get_volume(m_backend); }
|
|
int QobuzBackend::state() const { return qobuz_backend_get_state(m_backend); }
|
|
|
|
quint32 QobuzBackend::vizRead(float *buf, quint32 maxSamples)
|
|
{
|
|
return qobuz_backend_viz_read(m_backend, buf, maxSamples);
|
|
}
|
|
|
|
quint32 QobuzBackend::vizSampleRate() const
|
|
{
|
|
return qobuz_backend_viz_sample_rate(m_backend);
|
|
}
|
|
|
|
quint32 QobuzBackend::vizChannels() const
|
|
{
|
|
return qobuz_backend_viz_channels(m_backend);
|
|
}
|
|
|
|
// ---- private slots ----
|
|
|
|
void QobuzBackend::onPositionTick()
|
|
{
|
|
emit positionChanged(position(), duration());
|
|
|
|
if (qobuz_backend_take_track_finished(m_backend))
|
|
emit trackFinished();
|
|
}
|
|
|
|
void QobuzBackend::onEvent(int eventType, const QString &json)
|
|
{
|
|
const QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8());
|
|
if (!doc.isObject()) {
|
|
emit error(tr("Malformed response from backend"));
|
|
return;
|
|
}
|
|
const QJsonObject obj = doc.object();
|
|
|
|
switch (eventType) {
|
|
case EV_LOGIN_OK:
|
|
emit loginSuccess(obj["token"].toString(), obj["user"].toObject());
|
|
break;
|
|
case EV_LOGIN_ERR:
|
|
emit loginError(obj["error"].toString());
|
|
break;
|
|
case EV_SEARCH_OK:
|
|
emit searchResult(obj);
|
|
break;
|
|
case EV_SEARCH_ERR:
|
|
emit error(obj["error"].toString());
|
|
break;
|
|
case EV_ALBUM_OK:
|
|
emit albumLoaded(obj);
|
|
break;
|
|
case EV_ALBUM_ERR:
|
|
emit error(obj["error"].toString());
|
|
break;
|
|
case EV_ARTIST_OK:
|
|
emit artistLoaded(obj);
|
|
break;
|
|
case EV_ARTIST_ERR:
|
|
emit error(obj["error"].toString());
|
|
break;
|
|
case EV_PLAYLIST_OK:
|
|
emit playlistLoaded(obj);
|
|
break;
|
|
case EV_PLAYLIST_ERR:
|
|
emit error(obj["error"].toString());
|
|
break;
|
|
case EV_FAV_TRACKS_OK:
|
|
emit favTracksLoaded(obj);
|
|
break;
|
|
case EV_FAV_ALBUMS_OK:
|
|
emit favAlbumsLoaded(obj);
|
|
break;
|
|
case EV_FAV_ARTISTS_OK:
|
|
emit favArtistsLoaded(obj);
|
|
break;
|
|
case EV_PLAYLISTS_OK:
|
|
emit userPlaylistsLoaded(obj);
|
|
break;
|
|
case EV_TRACK_CHANGED:
|
|
emit trackChanged(obj);
|
|
break;
|
|
case EV_STATE_CHANGED:
|
|
emit stateChanged(obj["state"].toString());
|
|
break;
|
|
case 20: // EV_PLAYLIST_CREATED
|
|
emit playlistCreated(obj);
|
|
break;
|
|
case 21: // EV_PLAYLIST_DELETED
|
|
emit playlistDeleted(obj);
|
|
break;
|
|
case 22: // EV_PLAYLIST_TRACK_ADDED
|
|
emit playlistTrackAdded(static_cast<qint64>(obj["playlist_id"].toDouble()));
|
|
break;
|
|
case EV_USER_OK:
|
|
emit userLoaded(obj);
|
|
break;
|
|
case EV_GENERIC_ERR:
|
|
case EV_TRACK_URL_ERR:
|
|
emit error(obj["error"].toString());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ---- static trampoline ----
|
|
|
|
void QobuzBackend::eventTrampoline(void *userdata, int eventType, const char *json)
|
|
{
|
|
auto *self = static_cast<QobuzBackend *>(userdata);
|
|
// Marshal from Rust thread → Qt main thread
|
|
QMetaObject::invokeMethod(
|
|
self,
|
|
"onEvent",
|
|
Qt::QueuedConnection,
|
|
Q_ARG(int, eventType),
|
|
Q_ARG(QString, QString::fromUtf8(json))
|
|
);
|
|
}
|