Merge branch 'refactor/playqueue-split'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,9 @@ target_sources(qobuz-qt PRIVATE
|
|||||||
mainwindow.hpp
|
mainwindow.hpp
|
||||||
mainwindow.cpp
|
mainwindow.cpp
|
||||||
|
|
||||||
# Queue (header-only)
|
# Queue
|
||||||
playqueue.hpp
|
playqueue.hpp
|
||||||
|
playqueue.cpp
|
||||||
|
|
||||||
# Backend (Qt wrapper around Rust FFI)
|
# Backend (Qt wrapper around Rust FFI)
|
||||||
backend/qobuzbackend.hpp
|
backend/qobuzbackend.hpp
|
||||||
|
|||||||
238
src/playqueue.cpp
Normal file
238
src/playqueue.cpp
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
#include "playqueue.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
PlayQueue::PlayQueue(QObject *parent) : QObject(parent) {}
|
||||||
|
|
||||||
|
void PlayQueue::setContext(const QJsonArray &tracks, int startIndex)
|
||||||
|
{
|
||||||
|
m_queue.clear();
|
||||||
|
m_playNext.clear();
|
||||||
|
|
||||||
|
// Only queue streamable tracks; find the filtered index for startIndex
|
||||||
|
int filteredStart = 0;
|
||||||
|
int filteredIdx = 0;
|
||||||
|
bool found = false;
|
||||||
|
for (int orig = 0; orig < tracks.size(); ++orig) {
|
||||||
|
const QJsonObject t = tracks[orig].toObject();
|
||||||
|
if (!t["streamable"].toBool(true))
|
||||||
|
continue;
|
||||||
|
if (!found && orig >= startIndex) {
|
||||||
|
filteredStart = filteredIdx;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
m_queue.append(t);
|
||||||
|
++filteredIdx;
|
||||||
|
}
|
||||||
|
m_index = qBound(0, filteredStart, qMax(0, m_queue.size() - 1));
|
||||||
|
|
||||||
|
if (m_shuffle)
|
||||||
|
shuffleQueue(m_index);
|
||||||
|
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::reorderContext(const QJsonArray &tracks, qint64 currentId)
|
||||||
|
{
|
||||||
|
m_queue.clear();
|
||||||
|
for (const auto &v : tracks) {
|
||||||
|
const QJsonObject t = v.toObject();
|
||||||
|
if (t["streamable"].toBool(true))
|
||||||
|
m_queue.append(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_index = 0;
|
||||||
|
for (int i = 0; i < m_queue.size(); ++i) {
|
||||||
|
if (static_cast<qint64>(m_queue[i]["id"].toDouble()) == currentId) {
|
||||||
|
m_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::clearUpcoming()
|
||||||
|
{
|
||||||
|
m_playNext.clear();
|
||||||
|
if (m_index < m_queue.size())
|
||||||
|
m_queue.resize(m_index + 1); // keep up to and including current
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::removeUpcoming(int upcomingIndex)
|
||||||
|
{
|
||||||
|
if (upcomingIndex < m_playNext.size()) {
|
||||||
|
m_playNext.removeAt(upcomingIndex);
|
||||||
|
} else {
|
||||||
|
const int queueIdx = m_index + 1 + (upcomingIndex - m_playNext.size());
|
||||||
|
if (queueIdx < m_queue.size())
|
||||||
|
m_queue.removeAt(queueIdx);
|
||||||
|
}
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::setShuffle(bool enabled)
|
||||||
|
{
|
||||||
|
if (m_shuffle == enabled) return;
|
||||||
|
m_shuffle = enabled;
|
||||||
|
if (enabled && !m_queue.isEmpty())
|
||||||
|
shuffleQueue(m_index);
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::shuffleNow()
|
||||||
|
{
|
||||||
|
if (m_queue.isEmpty()) return;
|
||||||
|
shuffleQueue(m_index);
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::addToQueue(const QJsonObject &track)
|
||||||
|
{
|
||||||
|
m_playNext.append(track);
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::playNext(const QJsonObject &track)
|
||||||
|
{
|
||||||
|
m_playNext.prepend(track);
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayQueue::hasCurrent() const
|
||||||
|
{
|
||||||
|
return (!m_playNext.isEmpty()) || (!m_queue.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject PlayQueue::current() const
|
||||||
|
{
|
||||||
|
if (!m_playNext.isEmpty()) return m_playNext.first();
|
||||||
|
if (m_index < m_queue.size()) return m_queue.at(m_index);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 PlayQueue::currentId() const
|
||||||
|
{
|
||||||
|
return static_cast<qint64>(current()["id"].toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject PlayQueue::advance()
|
||||||
|
{
|
||||||
|
if (!m_playNext.isEmpty()) {
|
||||||
|
// Return the playNext item directly — do NOT call current() after
|
||||||
|
// removal, as that would fall back to the already-playing m_index track.
|
||||||
|
const QJsonObject next = m_playNext.takeFirst();
|
||||||
|
emit queueChanged();
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
++m_index;
|
||||||
|
emit queueChanged();
|
||||||
|
return current();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject PlayQueue::stepBack()
|
||||||
|
{
|
||||||
|
if (m_index > 0) --m_index;
|
||||||
|
emit queueChanged();
|
||||||
|
return current();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayQueue::canGoNext() const
|
||||||
|
{
|
||||||
|
return !m_playNext.isEmpty() || (m_index + 1 < m_queue.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::setCurrentById(qint64 id)
|
||||||
|
{
|
||||||
|
m_playNext.clear();
|
||||||
|
for (int i = 0; i < m_queue.size(); ++i) {
|
||||||
|
if (static_cast<qint64>(m_queue[i]["id"].toDouble()) == id) {
|
||||||
|
m_index = i;
|
||||||
|
emit queueChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QJsonObject> PlayQueue::upcomingTracks(int maxCount) const
|
||||||
|
{
|
||||||
|
QVector<QJsonObject> result;
|
||||||
|
result.append(m_playNext);
|
||||||
|
for (int i = m_index + 1; i < m_queue.size() && result.size() < maxCount; ++i)
|
||||||
|
result.append(m_queue.at(i));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject PlayQueue::skipToUpcoming(int upcomingIndex)
|
||||||
|
{
|
||||||
|
// Remove items 0..upcomingIndex-1 from the front of upcoming
|
||||||
|
for (int i = 0; i < upcomingIndex; ++i) {
|
||||||
|
if (!m_playNext.isEmpty())
|
||||||
|
m_playNext.removeFirst();
|
||||||
|
else if (m_index + 1 < m_queue.size())
|
||||||
|
++m_index;
|
||||||
|
}
|
||||||
|
// Pop and return the target (now at upcoming[0])
|
||||||
|
if (!m_playNext.isEmpty()) {
|
||||||
|
const QJsonObject t = m_playNext.takeFirst();
|
||||||
|
emit queueChanged();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
if (m_index + 1 < m_queue.size()) {
|
||||||
|
++m_index;
|
||||||
|
emit queueChanged();
|
||||||
|
return m_queue.at(m_index);
|
||||||
|
}
|
||||||
|
emit queueChanged();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::setUpcomingOrder(const QVector<QJsonObject> &newOrder)
|
||||||
|
{
|
||||||
|
m_playNext = newOrder;
|
||||||
|
m_queue.resize(m_index + 1); // drop old main-queue tail
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::appendToContext(const QJsonArray &tracks)
|
||||||
|
{
|
||||||
|
for (const auto &v : tracks) {
|
||||||
|
const QJsonObject t = v.toObject();
|
||||||
|
if (t["streamable"].toBool(true))
|
||||||
|
m_queue.append(t);
|
||||||
|
}
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::moveUpcomingToTop(int upcomingIndex)
|
||||||
|
{
|
||||||
|
if (upcomingIndex < 0) return;
|
||||||
|
QJsonObject track;
|
||||||
|
if (upcomingIndex < m_playNext.size()) {
|
||||||
|
if (upcomingIndex == 0) return; // already at top
|
||||||
|
track = m_playNext.takeAt(upcomingIndex);
|
||||||
|
} else {
|
||||||
|
const int queueIdx = m_index + 1 + (upcomingIndex - m_playNext.size());
|
||||||
|
if (queueIdx >= m_queue.size()) return;
|
||||||
|
track = m_queue.takeAt(queueIdx);
|
||||||
|
}
|
||||||
|
m_playNext.prepend(track);
|
||||||
|
emit queueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::shuffleQueue(int keepAtFront)
|
||||||
|
{
|
||||||
|
if (m_queue.isEmpty()) return;
|
||||||
|
// Keep the current track at index 0 of the remaining queue
|
||||||
|
if (keepAtFront >= 0 && keepAtFront < m_queue.size()) {
|
||||||
|
QJsonObject current = m_queue.takeAt(keepAtFront);
|
||||||
|
std::mt19937 rng(std::random_device{}());
|
||||||
|
std::shuffle(m_queue.begin(), m_queue.end(), rng);
|
||||||
|
m_queue.prepend(current);
|
||||||
|
} else {
|
||||||
|
std::mt19937 rng(std::random_device{}());
|
||||||
|
std::shuffle(m_queue.begin(), m_queue.end(), rng);
|
||||||
|
}
|
||||||
|
m_index = 0;
|
||||||
|
}
|
||||||
@@ -4,8 +4,6 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <algorithm>
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
/// Local playback queue. Holds the ordered list of tracks for the current
|
/// Local playback queue. Holds the ordered list of tracks for the current
|
||||||
/// context (album / playlist / search result / favourites) plus a separate
|
/// context (album / playlist / search result / favourites) plus a separate
|
||||||
@@ -15,259 +13,83 @@ class PlayQueue : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PlayQueue(QObject *parent = nullptr) : QObject(parent) {}
|
explicit PlayQueue(QObject *parent = nullptr);
|
||||||
|
|
||||||
// ---- Loading a new context ----
|
// ---- Loading a new context ----
|
||||||
|
|
||||||
/// Replace the queue with all tracks from an album/playlist JSON context.
|
/// Replace the queue with all tracks from an album/playlist JSON context.
|
||||||
/// @param startIndex Index of the track to start playing (-1 = first).
|
/// @param startIndex Index of the track to start playing (-1 = first).
|
||||||
void setContext(const QJsonArray &tracks, int startIndex = 0)
|
void setContext(const QJsonArray &tracks, int startIndex = 0);
|
||||||
{
|
|
||||||
m_queue.clear();
|
|
||||||
m_playNext.clear();
|
|
||||||
|
|
||||||
// Only queue streamable tracks; find the filtered index for startIndex
|
|
||||||
int filteredStart = 0;
|
|
||||||
int filteredIdx = 0;
|
|
||||||
bool found = false;
|
|
||||||
for (int orig = 0; orig < tracks.size(); ++orig) {
|
|
||||||
const QJsonObject t = tracks[orig].toObject();
|
|
||||||
if (!t["streamable"].toBool(true))
|
|
||||||
continue;
|
|
||||||
if (!found && orig >= startIndex) {
|
|
||||||
filteredStart = filteredIdx;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
m_queue.append(t);
|
|
||||||
++filteredIdx;
|
|
||||||
}
|
|
||||||
m_index = qBound(0, filteredStart, qMax(0, m_queue.size() - 1));
|
|
||||||
|
|
||||||
if (m_shuffle)
|
|
||||||
shuffleQueue(m_index);
|
|
||||||
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Re-order after a sort (keeps m_playNext, updates m_index) ----
|
// ---- Re-order after a sort (keeps m_playNext, updates m_index) ----
|
||||||
|
|
||||||
void reorderContext(const QJsonArray &tracks, qint64 currentId)
|
void reorderContext(const QJsonArray &tracks, qint64 currentId);
|
||||||
{
|
|
||||||
m_queue.clear();
|
|
||||||
for (const auto &v : tracks) {
|
|
||||||
const QJsonObject t = v.toObject();
|
|
||||||
if (t["streamable"].toBool(true))
|
|
||||||
m_queue.append(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_index = 0;
|
|
||||||
for (int i = 0; i < m_queue.size(); ++i) {
|
|
||||||
if (static_cast<qint64>(m_queue[i]["id"].toDouble()) == currentId) {
|
|
||||||
m_index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Clear / remove upcoming ----
|
// ---- Clear / remove upcoming ----
|
||||||
|
|
||||||
/// Remove all "up next" entries (playNext + remaining main queue after current).
|
/// Remove all "up next" entries (playNext + remaining main queue after current).
|
||||||
void clearUpcoming()
|
void clearUpcoming();
|
||||||
{
|
|
||||||
m_playNext.clear();
|
|
||||||
if (m_index < m_queue.size())
|
|
||||||
m_queue.resize(m_index + 1); // keep up to and including current
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove one upcoming track by its index in upcomingTracks().
|
/// Remove one upcoming track by its index in upcomingTracks().
|
||||||
void removeUpcoming(int upcomingIndex)
|
void removeUpcoming(int upcomingIndex);
|
||||||
{
|
|
||||||
if (upcomingIndex < m_playNext.size()) {
|
|
||||||
m_playNext.removeAt(upcomingIndex);
|
|
||||||
} else {
|
|
||||||
const int queueIdx = m_index + 1 + (upcomingIndex - m_playNext.size());
|
|
||||||
if (queueIdx < m_queue.size())
|
|
||||||
m_queue.removeAt(queueIdx);
|
|
||||||
}
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Shuffle ----
|
// ---- Shuffle ----
|
||||||
|
|
||||||
bool shuffleEnabled() const { return m_shuffle; }
|
bool shuffleEnabled() const { return m_shuffle; }
|
||||||
|
|
||||||
void setShuffle(bool enabled)
|
void setShuffle(bool enabled);
|
||||||
{
|
|
||||||
if (m_shuffle == enabled) return;
|
|
||||||
m_shuffle = enabled;
|
|
||||||
if (enabled && !m_queue.isEmpty())
|
|
||||||
shuffleQueue(m_index);
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shuffle the current queue once without changing the global shuffle flag.
|
/// Shuffle the current queue once without changing the global shuffle flag.
|
||||||
void shuffleNow()
|
void shuffleNow();
|
||||||
{
|
|
||||||
if (m_queue.isEmpty()) return;
|
|
||||||
shuffleQueue(m_index);
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Play-next prepend queue (like "Add to queue" ----
|
// ---- Play-next prepend queue (like "Add to queue" ----
|
||||||
|
|
||||||
void addToQueue(const QJsonObject &track)
|
void addToQueue(const QJsonObject &track);
|
||||||
{
|
|
||||||
m_playNext.append(track);
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void playNext(const QJsonObject &track)
|
void playNext(const QJsonObject &track);
|
||||||
{
|
|
||||||
m_playNext.prepend(track);
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Navigation ----
|
// ---- Navigation ----
|
||||||
|
|
||||||
bool hasCurrent() const
|
bool hasCurrent() const;
|
||||||
{
|
|
||||||
return (!m_playNext.isEmpty()) || (!m_queue.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject current() const
|
QJsonObject current() const;
|
||||||
{
|
|
||||||
if (!m_playNext.isEmpty()) return m_playNext.first();
|
|
||||||
if (m_index < m_queue.size()) return m_queue.at(m_index);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 currentId() const
|
qint64 currentId() const;
|
||||||
{
|
|
||||||
return static_cast<qint64>(current()["id"].toDouble());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance and return the track to play next. Returns {} at end of queue.
|
/// Advance and return the track to play next. Returns {} at end of queue.
|
||||||
QJsonObject advance()
|
QJsonObject advance();
|
||||||
{
|
|
||||||
if (!m_playNext.isEmpty()) {
|
|
||||||
// Return the playNext item directly — do NOT call current() after
|
|
||||||
// removal, as that would fall back to the already-playing m_index track.
|
|
||||||
const QJsonObject next = m_playNext.takeFirst();
|
|
||||||
emit queueChanged();
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
++m_index;
|
|
||||||
emit queueChanged();
|
|
||||||
return current();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Step backwards in the main queue (play-next is not affected).
|
/// Step backwards in the main queue (play-next is not affected).
|
||||||
QJsonObject stepBack()
|
QJsonObject stepBack();
|
||||||
{
|
|
||||||
if (m_index > 0) --m_index;
|
|
||||||
emit queueChanged();
|
|
||||||
return current();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool canGoNext() const
|
bool canGoNext() const;
|
||||||
{
|
|
||||||
return !m_playNext.isEmpty() || (m_index + 1 < m_queue.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool canGoPrev() const { return m_index > 0; }
|
bool canGoPrev() const { return m_index > 0; }
|
||||||
|
|
||||||
// ---- Index lookup ----
|
// ---- Index lookup ----
|
||||||
|
|
||||||
/// Set the current position by track id (after user double-clicks a row).
|
/// Set the current position by track id (after user double-clicks a row).
|
||||||
void setCurrentById(qint64 id)
|
void setCurrentById(qint64 id);
|
||||||
{
|
|
||||||
m_playNext.clear();
|
|
||||||
for (int i = 0; i < m_queue.size(); ++i) {
|
|
||||||
if (static_cast<qint64>(m_queue[i]["id"].toDouble()) == id) {
|
|
||||||
m_index = i;
|
|
||||||
emit queueChanged();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Accessors for queue panel ----
|
// ---- Accessors for queue panel ----
|
||||||
|
|
||||||
QVector<QJsonObject> upcomingTracks(int maxCount = 200) const
|
QVector<QJsonObject> upcomingTracks(int maxCount = 200) const;
|
||||||
{
|
|
||||||
QVector<QJsonObject> result;
|
|
||||||
result.append(m_playNext);
|
|
||||||
for (int i = m_index + 1; i < m_queue.size() && result.size() < maxCount; ++i)
|
|
||||||
result.append(m_queue.at(i));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int playNextCount() const { return m_playNext.size(); }
|
int playNextCount() const { return m_playNext.size(); }
|
||||||
int totalSize() const { return m_playNext.size() + m_queue.size(); }
|
int totalSize() const { return m_playNext.size() + m_queue.size(); }
|
||||||
int currentIndex() const { return m_index; }
|
int currentIndex() const { return m_index; }
|
||||||
|
|
||||||
/// Skip to upcoming[upcomingIndex]: removes everything before it, pops and returns it.
|
/// Skip to upcoming[upcomingIndex]: removes everything before it, pops and returns it.
|
||||||
QJsonObject skipToUpcoming(int upcomingIndex)
|
QJsonObject skipToUpcoming(int upcomingIndex);
|
||||||
{
|
|
||||||
// Remove items 0..upcomingIndex-1 from the front of upcoming
|
|
||||||
for (int i = 0; i < upcomingIndex; ++i) {
|
|
||||||
if (!m_playNext.isEmpty())
|
|
||||||
m_playNext.removeFirst();
|
|
||||||
else if (m_index + 1 < m_queue.size())
|
|
||||||
++m_index;
|
|
||||||
}
|
|
||||||
// Pop and return the target (now at upcoming[0])
|
|
||||||
if (!m_playNext.isEmpty()) {
|
|
||||||
const QJsonObject t = m_playNext.takeFirst();
|
|
||||||
emit queueChanged();
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
if (m_index + 1 < m_queue.size()) {
|
|
||||||
++m_index;
|
|
||||||
emit queueChanged();
|
|
||||||
return m_queue.at(m_index);
|
|
||||||
}
|
|
||||||
emit queueChanged();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace the upcoming list with a new order (used after drag-reorder in UI).
|
/// Replace the upcoming list with a new order (used after drag-reorder in UI).
|
||||||
void setUpcomingOrder(const QVector<QJsonObject> &newOrder)
|
void setUpcomingOrder(const QVector<QJsonObject> &newOrder);
|
||||||
{
|
|
||||||
m_playNext = newOrder;
|
|
||||||
m_queue.resize(m_index + 1); // drop old main-queue tail
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append tracks to the main queue tail (autoplay/discovery).
|
/// Append tracks to the main queue tail (autoplay/discovery).
|
||||||
void appendToContext(const QJsonArray &tracks)
|
void appendToContext(const QJsonArray &tracks);
|
||||||
{
|
|
||||||
for (const auto &v : tracks) {
|
|
||||||
const QJsonObject t = v.toObject();
|
|
||||||
if (t["streamable"].toBool(true))
|
|
||||||
m_queue.append(t);
|
|
||||||
}
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move an upcoming item (by its index in upcomingTracks()) to the front of playNext.
|
/// Move an upcoming item (by its index in upcomingTracks()) to the front of playNext.
|
||||||
void moveUpcomingToTop(int upcomingIndex)
|
void moveUpcomingToTop(int upcomingIndex);
|
||||||
{
|
|
||||||
if (upcomingIndex < 0) return;
|
|
||||||
QJsonObject track;
|
|
||||||
if (upcomingIndex < m_playNext.size()) {
|
|
||||||
if (upcomingIndex == 0) return; // already at top
|
|
||||||
track = m_playNext.takeAt(upcomingIndex);
|
|
||||||
} else {
|
|
||||||
const int queueIdx = m_index + 1 + (upcomingIndex - m_playNext.size());
|
|
||||||
if (queueIdx >= m_queue.size()) return;
|
|
||||||
track = m_queue.takeAt(queueIdx);
|
|
||||||
}
|
|
||||||
m_playNext.prepend(track);
|
|
||||||
emit queueChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void queueChanged();
|
void queueChanged();
|
||||||
@@ -278,19 +100,5 @@ private:
|
|||||||
int m_index = 0;
|
int m_index = 0;
|
||||||
bool m_shuffle = false;
|
bool m_shuffle = false;
|
||||||
|
|
||||||
void shuffleQueue(int keepAtFront)
|
void shuffleQueue(int keepAtFront);
|
||||||
{
|
|
||||||
if (m_queue.isEmpty()) return;
|
|
||||||
// Keep the current track at index 0 of the remaining queue
|
|
||||||
if (keepAtFront >= 0 && keepAtFront < m_queue.size()) {
|
|
||||||
QJsonObject current = m_queue.takeAt(keepAtFront);
|
|
||||||
std::mt19937 rng(std::random_device{}());
|
|
||||||
std::shuffle(m_queue.begin(), m_queue.end(), rng);
|
|
||||||
m_queue.prepend(current);
|
|
||||||
} else {
|
|
||||||
std::mt19937 rng(std::random_device{}());
|
|
||||||
std::shuffle(m_queue.begin(), m_queue.end(), rng);
|
|
||||||
}
|
|
||||||
m_index = 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user