Initial implementation of qobuz-qt

- Rust backend (qobuz-backend static lib): Qobuz API client (reqwest/tokio),
  Symphonia audio decoder, CPAL audio output, extern "C" FFI bridge
- Qt 6 frontend mirroring spotify-qt layout: toolbar with playback controls,
  left library dock, central track list, right search panel
- Auth: email/password login with MD5-signed requests; session token persisted
  via QSettings
- Playback: double-click a track → Rust fetches stream URL → Symphonia decodes
  → CPAL outputs to default audio device
- Dark Fusion palette matching spotify-qt feel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-23 23:34:23 +01:00
commit 9402dca7ed
40 changed files with 3963 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
#pragma once
#include "qobuz_backend.h"
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QTimer>
/// Qt wrapper around the Rust qobuz-backend static library.
///
/// All signals are emitted on the Qt main thread regardless of which thread
/// the Rust callback fires on (marshalled via QMetaObject::invokeMethod with
/// Qt::QueuedConnection).
class QobuzBackend : public QObject
{
Q_OBJECT
public:
explicit QobuzBackend(QObject *parent = nullptr);
~QobuzBackend() override;
// --- auth ---
void login(const QString &email, const QString &password);
void setToken(const QString &token);
// --- catalog ---
void search(const QString &query, quint32 offset = 0, quint32 limit = 20);
void getAlbum(const QString &albumId);
void getArtist(qint64 artistId);
void getPlaylist(qint64 playlistId, quint32 offset = 0, quint32 limit = 500);
// --- favorites ---
void getFavTracks(quint32 offset = 0, quint32 limit = 500);
void getFavAlbums(quint32 offset = 0, quint32 limit = 200);
void getFavArtists(quint32 offset = 0, quint32 limit = 200);
void getUserPlaylists(quint32 offset = 0, quint32 limit = 200);
// --- fav modification ---
void addFavTrack(qint64 trackId);
void removeFavTrack(qint64 trackId);
void addFavAlbum(const QString &albumId);
void removeFavAlbum(const QString &albumId);
// --- playback ---
void playTrack(qint64 trackId, int formatId = 6);
void pause();
void resume();
void stop();
void setVolume(int volume);
quint64 position() const;
quint64 duration() const;
int volume() const;
/// 1 = playing, 2 = paused, 0 = idle
int state() const;
signals:
// auth
void loginSuccess(const QString &token, const QJsonObject &user);
void loginError(const QString &error);
// catalog
void searchResult(const QJsonObject &result);
void albumLoaded(const QJsonObject &album);
void artistLoaded(const QJsonObject &artist);
void playlistLoaded(const QJsonObject &playlist);
// favorites
void favTracksLoaded(const QJsonObject &result);
void favAlbumsLoaded(const QJsonObject &result);
void favArtistsLoaded(const QJsonObject &result);
void userPlaylistsLoaded(const QJsonObject &result);
// playback
void trackChanged(const QJsonObject &track);
void stateChanged(const QString &state);
void positionChanged(quint64 position, quint64 duration);
void trackFinished();
// errors
void error(const QString &message);
private slots:
Q_INVOKABLE void onEvent(int eventType, const QString &json);
void onPositionTick();
private:
QobuzBackendOpaque *m_backend = nullptr;
QTimer *m_positionTimer = nullptr;
// Static trampoline called from Rust threads
static void eventTrampoline(void *userdata, int eventType, const char *json);
};