diff options
Diffstat (limited to 'src/tangara/audio/track_queue.cpp')
| -rw-r--r-- | src/tangara/audio/track_queue.cpp | 282 |
1 files changed, 151 insertions, 131 deletions
diff --git a/src/tangara/audio/track_queue.cpp b/src/tangara/audio/track_queue.cpp index 603b0de1..2c1faf96 100644 --- a/src/tangara/audio/track_queue.cpp +++ b/src/tangara/audio/track_queue.cpp @@ -26,7 +26,9 @@ #include "database/track.hpp" #include "events/event_queue.hpp" #include "memory_resource.hpp" +#include "random.hpp" #include "tasks.hpp" +#include "track_queue.hpp" #include "ui/ui_fsm.hpp" namespace audio { @@ -83,93 +85,119 @@ auto notifyChanged(bool current_changed, Reason reason) -> void { events::Audio().Dispatch(ev); } -TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker) +TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db) : mutex_(), bg_worker_(bg_worker), - pos_(0), - tracks_(&memory::kSpiRamResource), + db_(db), + playlist_(".queue.playlist"), + position_(0), shuffle_(), repeat_(false), replay_(false) {} -auto TrackQueue::current() const -> std::optional<database::TrackId> { +auto TrackQueue::current() const -> TrackItem { const std::shared_lock<std::shared_mutex> lock(mutex_); - if (pos_ >= tracks_.size()) { + std::string val; + if (opened_playlist_ && position_ < opened_playlist_->size()) { + val = opened_playlist_->value(); + } else { + val = playlist_.value(); + } + if (val.empty()) { return {}; } - return tracks_[pos_]; + return val; } -auto TrackQueue::peekNext(std::size_t limit) const - -> std::vector<database::TrackId> { +auto TrackQueue::currentPosition() const -> size_t { const std::shared_lock<std::shared_mutex> lock(mutex_); - std::vector<database::TrackId> out; - for (size_t i = pos_ + 1; i < pos_ + limit + 1 && i < tracks_.size(); i++) { - out.push_back(i); + return position_; +} + +auto TrackQueue::totalSize() const -> size_t { + size_t sum = playlist_.size(); + if (opened_playlist_) { + sum += opened_playlist_->size(); } - return out; + return sum; } -auto TrackQueue::peekPrevious(std::size_t limit) const - -> std::vector<database::TrackId> { - const std::shared_lock<std::shared_mutex> lock(mutex_); - std::vector<database::TrackId> out; - for (size_t i = pos_ - 1; i < pos_ - limit - 1 && i >= tracks_.size(); i--) { - out.push_back(i); +auto TrackQueue::updateShuffler(bool andUpdatePosition) -> void { + if (shuffle_) { + shuffle_->resize(totalSize()); + if (andUpdatePosition) { + goTo(shuffle_->current()); + } } - return out; } -auto TrackQueue::currentPosition() const -> size_t { - const std::shared_lock<std::shared_mutex> lock(mutex_); - return pos_; +auto TrackQueue::open() -> bool { + // FIX ME: If playlist opening fails, should probably fall back to a vector of + // tracks or something so that we're not necessarily always needing mounted + // storage + return playlist_.open(); } -auto TrackQueue::totalSize() const -> size_t { - const std::shared_lock<std::shared_mutex> lock(mutex_); - return tracks_.size(); +auto TrackQueue::openPlaylist(const std::string& playlist_file, bool notify) + -> bool { + opened_playlist_.emplace(playlist_file); + auto res = opened_playlist_->open(); + if (!res) { + return false; + } + updateShuffler(true); + if (notify) { + notifyChanged(true, Reason::kExplicitUpdate); + } + return true; } -auto TrackQueue::insert(Item i, size_t index) -> void { +auto TrackQueue::getFilepath(database::TrackId id) + -> std::optional<std::string> { + auto db = db_.lock(); + if (!db) { + return {}; + } + return db->getTrackPath(id); +} + +auto TrackQueue::append(Item i) -> void { bool was_queue_empty; bool current_changed; { const std::shared_lock<std::shared_mutex> lock(mutex_); - was_queue_empty = pos_ == tracks_.size(); - current_changed = was_queue_empty || index == pos_; + was_queue_empty = playlist_.currentPosition() >= playlist_.size(); + current_changed = was_queue_empty; // Dont support inserts yet } - auto update_shuffler = [=, this]() { - if (shuffle_) { - shuffle_->resize(tracks_.size()); - // If there wasn't anything already playing, then we should make sure we - // begin playback at a random point, instead of always starting with - // whatever was inserted first and *then* shuffling. - // We don't base this purely off of current_changed because we would like - // 'play this track now' (by inserting at the current pos) to work even - // when shuffling is enabled. - if (was_queue_empty) { - pos_ = shuffle_->current(); - } - } - }; - if (std::holds_alternative<database::TrackId>(i)) { { const std::unique_lock<std::shared_mutex> lock(mutex_); - if (index <= tracks_.size()) { - tracks_.insert(tracks_.begin() + index, std::get<database::TrackId>(i)); - update_shuffler(); + auto filename = getFilepath(std::get<database::TrackId>(i)).value_or(""); + if (!filename.empty()) { + playlist_.append(filename); } + updateShuffler(was_queue_empty); } notifyChanged(current_changed, Reason::kExplicitUpdate); + } else if (std::holds_alternative<std::string>(i)) { + auto& path = std::get<std::string>(i); + if (!path.empty()) { + { + const std::unique_lock<std::shared_mutex> lock(mutex_); + playlist_.append(std::get<std::string>(i)); + updateShuffler(was_queue_empty); + } + notifyChanged(current_changed, Reason::kExplicitUpdate); + } } else if (std::holds_alternative<database::TrackIterator>(i)) { // Iterators can be very large, and retrieving items from them often // requires disk i/o. Handle them asynchronously so that inserting them // doesn't block. bg_worker_.Dispatch<void>([=, this]() { database::TrackIterator it = std::get<database::TrackIterator>(i); - size_t working_pos = index; + + size_t next_update_at = 10; while (true) { auto next = *it; if (!next) { @@ -179,33 +207,61 @@ auto TrackQueue::insert(Item i, size_t index) -> void { // like current(). { const std::unique_lock<std::shared_mutex> lock(mutex_); - if (working_pos <= tracks_.size()) { - tracks_.insert(tracks_.begin() + working_pos, *next); + auto filename = getFilepath(*next).value_or(""); + if (!filename.empty()) { + playlist_.append(filename); } } - working_pos++; it++; + + // Appending very large iterators can take a while. Send out periodic + // queue updates during them so that the user has an idea what's going + // on. + if (!--next_update_at) { + next_update_at = util::sRandom->RangeInclusive(10, 20); + notifyChanged(false, Reason::kBulkLoadingUpdate); + } } + { const std::unique_lock<std::shared_mutex> lock(mutex_); - update_shuffler(); + updateShuffler(was_queue_empty); } notifyChanged(current_changed, Reason::kExplicitUpdate); }); } } -auto TrackQueue::append(Item i) -> void { - size_t end; +auto TrackQueue::next() -> void { + next(Reason::kExplicitUpdate); +} + +auto TrackQueue::currentPosition(size_t position) -> bool { { const std::shared_lock<std::shared_mutex> lock(mutex_); - end = tracks_.size(); + if (position >= totalSize()) { + return false; + } + goTo(position); } - insert(i, end); + + // If we're explicitly setting the position, we want to treat it as though + // the current track has changed, even if the position was the same + notifyChanged(true, Reason::kExplicitUpdate); + return true; } -auto TrackQueue::next() -> void { - next(Reason::kExplicitUpdate); +auto TrackQueue::goTo(size_t position) -> void { + position_ = position; + if (opened_playlist_) { + if (position_ < opened_playlist_->size()) { + opened_playlist_->skipTo(position_); + } else { + playlist_.skipTo(position_ - opened_playlist_->size()); + } + } else { + playlist_.skipTo(position_); + } } auto TrackQueue::next(Reason r) -> void { @@ -213,21 +269,19 @@ auto TrackQueue::next(Reason r) -> void { { const std::unique_lock<std::shared_mutex> lock(mutex_); + auto pos = position_; + if (shuffle_) { shuffle_->next(); - pos_ = shuffle_->current(); + position_ = shuffle_->current(); } else { - if (pos_ + 1 >= tracks_.size()) { - if (replay_) { - pos_ = 0; - } else { - pos_ = tracks_.size(); - changed = false; - } - } else { - pos_++; + if (position_ + 1 < totalSize()) { + position_++; } } + + goTo(position_); + changed = pos != position_; } notifyChanged(changed, r); @@ -240,18 +294,13 @@ auto TrackQueue::previous() -> void { const std::unique_lock<std::shared_mutex> lock(mutex_); if (shuffle_) { shuffle_->prev(); - pos_ = shuffle_->current(); + position_ = shuffle_->current(); } else { - if (pos_ == 0) { - if (repeat_) { - pos_ = tracks_.size() - 1; - } else { - changed = false; - } - } else { - pos_--; + if (position_ > 0) { + position_--; } } + goTo(position_); } notifyChanged(changed, Reason::kExplicitUpdate); @@ -265,39 +314,12 @@ auto TrackQueue::finish() -> void { } } -auto TrackQueue::skipTo(database::TrackId id) -> void { - // Defer this work to the background not because it's particularly - // long-running (although it could be), but because we want to ensure we - // only search for the given id after any previously pending iterator - // insertions have finished. - bg_worker_.Dispatch<void>([=, this]() { - bool found = false; - { - const std::unique_lock<std::shared_mutex> lock(mutex_); - for (size_t i = 0; i < tracks_.size(); i++) { - if (tracks_[i] == id) { - pos_ = i; - found = true; - break; - } - } - } - if (found) { - notifyChanged(true, Reason::kExplicitUpdate); - } - }); -} - auto TrackQueue::clear() -> void { { const std::unique_lock<std::shared_mutex> lock(mutex_); - if (tracks_.empty()) { - return; - } - - pos_ = 0; - tracks_.clear(); - + position_ = 0; + playlist_.clear(); + opened_playlist_.reset(); if (shuffle_) { shuffle_->resize(0); } @@ -309,10 +331,8 @@ auto TrackQueue::clear() -> void { auto TrackQueue::random(bool en) -> void { { const std::unique_lock<std::shared_mutex> lock(mutex_); - // Don't check for en == true already; this has the side effect that - // repeated calls with en == true will re-shuffle. if (en) { - shuffle_.emplace(tracks_.size()); + shuffle_.emplace(totalSize()); shuffle_->replay(replay_); } else { shuffle_.reset(); @@ -360,15 +380,20 @@ auto TrackQueue::replay() const -> bool { auto TrackQueue::serialise() -> std::string { cppbor::Array tracks{}; - for (database::TrackId track : tracks_) { - tracks.add(cppbor::Uint(track)); - } cppbor::Map encoded; - encoded.add(cppbor::Uint{0}, cppbor::Array{ - cppbor::Uint{pos_}, - cppbor::Bool{repeat_}, - cppbor::Bool{replay_}, - }); + + cppbor::Array metadata{ + cppbor::Bool{repeat_}, + cppbor::Bool{replay_}, + cppbor::Uint{position_}, + }; + + if (opened_playlist_) { + metadata.add(cppbor::Tstr{opened_playlist_->filepath()}); + } + + encoded.add(cppbor::Uint{0}, std::move(metadata)); + if (shuffle_) { encoded.add(cppbor::Uint{1}, cppbor::Array{ cppbor::Uint{shuffle_->size()}, @@ -376,12 +401,11 @@ auto TrackQueue::serialise() -> std::string { cppbor::Uint{shuffle_->pos()}, }); } - encoded.add(cppbor::Uint{2}, std::move(tracks)); return encoded.toString(); } TrackQueue::QueueParseClient::QueueParseClient(TrackQueue& queue) - : queue_(queue), state_(State::kInit), i_(0) {} + : queue_(queue), state_(State::kInit), i_(0), position_to_set_(0) {} cppbor::ParseClient* TrackQueue::QueueParseClient::item( std::unique_ptr<cppbor::Item>& item, @@ -401,9 +425,6 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item( case 1: state_ = State::kShuffle; break; - case 2: - state_ = State::kTracks; - break; default: state_ = State::kFinished; } @@ -412,7 +433,13 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item( if (item->type() == cppbor::ARRAY) { i_ = 0; } else if (item->type() == cppbor::UINT) { - queue_.pos_ = item->asUint()->unsignedValue(); + auto val = item->asUint()->unsignedValue(); + // Save the position so we can apply it later when we have finished + // serialising + position_to_set_ = val; + } else if (item->type() == cppbor::TSTR) { + auto val = item->asTstr(); + queue_.openPlaylist(val->value(), false); } else if (item->type() == cppbor::SIMPLE) { bool val = item->asBool()->value(); if (i_ == 0) { @@ -444,10 +471,6 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item( } i_++; } - } else if (state_ == State::kTracks) { - if (item->type() == cppbor::UINT) { - queue_.tracks_.push_back(item->asUint()->unsignedValue()); - } } else if (state_ == State::kFinished) { } return this; @@ -461,6 +484,7 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::itemEnd( if (state_ == State::kInit) { state_ = State::kFinished; } else if (state_ == State::kRoot) { + queue_.goTo(position_to_set_); state_ = State::kFinished; } else if (state_ == State::kMetadata) { if (item->type() == cppbor::ARRAY) { @@ -470,10 +494,6 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::itemEnd( if (item->type() == cppbor::ARRAY) { state_ = State::kRoot; } - } else if (state_ == State::kTracks) { - if (item->type() == cppbor::ARRAY) { - state_ = State::kRoot; - } } else if (state_ == State::kFinished) { } return this; |
