- 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>
114 lines
3.2 KiB
C++
114 lines
3.2 KiB
C++
#include "tracklistmodel.hpp"
|
|
|
|
#include <QJsonValue>
|
|
#include <QColor>
|
|
|
|
TrackListModel::TrackListModel(QObject *parent)
|
|
: QAbstractTableModel(parent)
|
|
{}
|
|
|
|
void TrackListModel::setTracks(const QJsonArray &tracks)
|
|
{
|
|
beginResetModel();
|
|
m_tracks.clear();
|
|
m_tracks.reserve(tracks.size());
|
|
|
|
for (const QJsonValue &v : tracks) {
|
|
const QJsonObject t = v.toObject();
|
|
TrackItem item;
|
|
item.id = static_cast<qint64>(t["id"].toDouble());
|
|
item.number = t["track_number"].toInt();
|
|
item.title = t["title"].toString();
|
|
item.duration = static_cast<qint64>(t["duration"].toDouble());
|
|
item.hiRes = t["hires_streamable"].toBool();
|
|
item.streamable = t["streamable"].toBool();
|
|
item.raw = t;
|
|
|
|
// Performer / artist
|
|
const QJsonObject performer = t["performer"].toObject();
|
|
item.artist = performer["name"].toString();
|
|
if (item.artist.isEmpty()) {
|
|
const QJsonObject album = t["album"].toObject();
|
|
const QJsonObject artist = album["artist"].toObject();
|
|
item.artist = artist["name"].toString();
|
|
}
|
|
|
|
// Album
|
|
const QJsonObject album = t["album"].toObject();
|
|
item.album = album["title"].toString();
|
|
item.albumId = album["id"].toString();
|
|
|
|
m_tracks.append(item);
|
|
}
|
|
endResetModel();
|
|
}
|
|
|
|
void TrackListModel::clear()
|
|
{
|
|
beginResetModel();
|
|
m_tracks.clear();
|
|
endResetModel();
|
|
}
|
|
|
|
int TrackListModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid()) return 0;
|
|
return m_tracks.size();
|
|
}
|
|
|
|
int TrackListModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid()) return 0;
|
|
return ColCount;
|
|
}
|
|
|
|
QVariant TrackListModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid() || index.row() >= m_tracks.size())
|
|
return {};
|
|
|
|
const TrackItem &t = m_tracks.at(index.row());
|
|
|
|
if (role == Qt::DisplayRole) {
|
|
switch (index.column()) {
|
|
case ColNumber: return t.number > 0 ? QString::number(t.number) : QString();
|
|
case ColTitle: return t.title;
|
|
case ColArtist: return t.artist;
|
|
case ColAlbum: return t.album;
|
|
case ColDuration: return formatDuration(t.duration);
|
|
}
|
|
}
|
|
|
|
if (role == Qt::ForegroundRole && !t.streamable) {
|
|
return QColor(Qt::gray);
|
|
}
|
|
|
|
if (role == TrackIdRole) return t.id;
|
|
if (role == TrackJsonRole) return t.raw;
|
|
if (role == HiResRole) return t.hiRes;
|
|
|
|
return {};
|
|
}
|
|
|
|
QVariant TrackListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
|
|
return {};
|
|
|
|
switch (section) {
|
|
case ColNumber: return tr("#");
|
|
case ColTitle: return tr("Title");
|
|
case ColArtist: return tr("Artist");
|
|
case ColAlbum: return tr("Album");
|
|
case ColDuration: return tr("Duration");
|
|
}
|
|
return {};
|
|
}
|
|
|
|
QString TrackListModel::formatDuration(qint64 secs)
|
|
{
|
|
const int m = static_cast<int>(secs / 60);
|
|
const int s = static_cast<int>(secs % 60);
|
|
return QStringLiteral("%1:%2").arg(m).arg(s, 2, 10, QLatin1Char('0'));
|
|
}
|