feat: add seamless lazy loading for genre and playlist views
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:
joren
2026-03-31 10:43:36 +02:00
parent 4ebd5ed3f0
commit e453f8acf3
12 changed files with 543 additions and 17 deletions

View File

@@ -109,6 +109,92 @@ void TrackListModel::setTracks(const QJsonArray &tracks,
emit sortApplied();
}
void TrackListModel::appendTracks(const QJsonArray &tracks,
bool usePosition,
bool useSequential)
{
if (tracks.isEmpty())
return;
// Keep append path simple and stable: disc-header mode is handled by reset path.
if (m_hasMultipleDiscs && !usePosition && !useSequential) {
QJsonArray all = currentTracksJson();
for (const QJsonValue &v : tracks)
all.append(v);
setTracks(all, usePosition, useSequential);
return;
}
int seq = 1;
if (useSequential || usePosition) {
for (const TrackItem &t : m_tracks)
if (!t.isDiscHeader)
++seq;
}
QVector<TrackItem> parsed;
parsed.reserve(tracks.size());
for (const QJsonValue &v : tracks) {
const QJsonObject t = v.toObject();
TrackItem item;
item.id = static_cast<qint64>(t["id"].toDouble());
item.playlistTrackId = static_cast<qint64>(t["playlist_track_id"].toDouble());
item.discNumber = t["media_number"].toInt(1);
item.duration = static_cast<qint64>(t["duration"].toDouble());
item.streamable = t["streamable"].toBool(true);
item.hiRes = t["hires_streamable"].toBool();
item.raw = t;
const QString base = t["title"].toString();
const QString version = t["version"].toString().trimmed();
item.title = version.isEmpty() ? base
: base + QStringLiteral(" (") + version + QLatin1Char(')');
if (useSequential) {
item.number = seq++;
} else if (usePosition) {
const int pos = t["position"].toInt();
item.number = pos > 0 ? pos : seq;
++seq;
} else {
item.number = t["track_number"].toInt();
}
const QJsonObject performer = t["performer"].toObject();
item.artist = performer["name"].toString();
if (item.artist.isEmpty()) {
const QJsonValue n = t["album"].toObject()["artist"].toObject()["name"];
item.artist = n.isObject() ? n.toObject()["display"].toString() : n.toString();
}
if (item.artist.isEmpty()) {
const QJsonValue n = t["artist"].toObject()["name"];
item.artist = n.isObject() ? n.toObject()["display"].toString() : n.toString();
}
const QJsonObject album = t["album"].toObject();
item.album = album["title"].toString();
item.albumId = album["id"].toString();
parsed.append(item);
}
if (parsed.isEmpty())
return;
const int first = m_tracks.size();
const int last = first + parsed.size() - 1;
beginInsertRows({}, first, last);
m_tracks += parsed;
endInsertRows();
if (!m_hasMultipleDiscs && m_sortColumn >= 0) {
emit layoutAboutToBeChanged();
sortData(m_sortColumn, m_sortOrder);
emit layoutChanged();
emit sortApplied();
}
}
void TrackListModel::clear()
{
beginResetModel();