commit
b6e0e0dd4a
@ -0,0 +1,85 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <deque> |
||||||
|
#include <mutex> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "track.hpp" |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
/*
|
||||||
|
* Owns and manages a complete view of the playback queue. Includes the |
||||||
|
* currently playing track, a truncated list of previously played tracks, and |
||||||
|
* all future tracks that have been queued. |
||||||
|
* |
||||||
|
* In order to not use all of our memory, this class deals strictly with track |
||||||
|
* ids. Consumers that need more data than this should fetch it from the |
||||||
|
* database. |
||||||
|
* |
||||||
|
* Instances of this class are broadly safe to use from multiple tasks; each |
||||||
|
* method represents an atomic operation. No guarantees are made about |
||||||
|
* consistency between calls however. For example, there may be data changes |
||||||
|
* between consecutive calls to AddNext() and GetUpcoming(); |
||||||
|
*/ |
||||||
|
class TrackQueue { |
||||||
|
public: |
||||||
|
TrackQueue(); |
||||||
|
|
||||||
|
/* Returns the currently playing track. */ |
||||||
|
auto GetCurrent() const -> std::optional<database::TrackId>; |
||||||
|
/* Returns, in order, tracks that have been queued to be played next. */ |
||||||
|
auto GetUpcoming(std::size_t limit) const -> std::vector<database::TrackId>; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Enqueues a track, placing it immediately after the current track and |
||||||
|
* before anything already queued. |
||||||
|
* |
||||||
|
* If there is no current track, the given track will begin playback. |
||||||
|
*/ |
||||||
|
auto AddNext(database::TrackId) -> void; |
||||||
|
auto AddNext(const std::vector<database::TrackId>&) -> void; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Enqueues a track, placing it the end of all enqueued tracks. |
||||||
|
* |
||||||
|
* If there is no current track, the given track will begin playback. |
||||||
|
*/ |
||||||
|
auto AddLast(database::TrackId) -> void; |
||||||
|
auto AddLast(const std::vector<database::TrackId>&) -> void; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Advances to the next track in the queue, placing the current track at the |
||||||
|
* front of the 'played' queue. |
||||||
|
*/ |
||||||
|
auto Next() -> void; |
||||||
|
auto Previous() -> void; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Removes all tracks from all queues, and stops any currently playing track. |
||||||
|
*/ |
||||||
|
auto Clear() -> void; |
||||||
|
/*
|
||||||
|
* Removes a specific track from the queue of upcoming tracks. Has no effect |
||||||
|
* on the currently playing track. |
||||||
|
*/ |
||||||
|
auto RemoveUpcoming(database::TrackId) -> void; |
||||||
|
|
||||||
|
TrackQueue(const TrackQueue&) = delete; |
||||||
|
TrackQueue& operator=(const TrackQueue&) = delete; |
||||||
|
|
||||||
|
private: |
||||||
|
mutable std::mutex mutex_; |
||||||
|
|
||||||
|
std::deque<database::TrackId> played_; |
||||||
|
std::deque<database::TrackId> upcoming_; |
||||||
|
std::optional<database::TrackId> current_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace audio
|
@ -0,0 +1,128 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "track_queue.hpp" |
||||||
|
|
||||||
|
#include <algorithm> |
||||||
|
#include <mutex> |
||||||
|
|
||||||
|
#include "audio_events.hpp" |
||||||
|
#include "audio_fsm.hpp" |
||||||
|
#include "event_queue.hpp" |
||||||
|
#include "track.hpp" |
||||||
|
#include "ui_fsm.hpp" |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
TrackQueue::TrackQueue() {} |
||||||
|
|
||||||
|
auto TrackQueue::GetCurrent() const -> std::optional<database::TrackId> { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
return current_; |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::GetUpcoming(std::size_t limit) const |
||||||
|
-> std::vector<database::TrackId> { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
std::vector<database::TrackId> ret; |
||||||
|
limit = std::min(limit, upcoming_.size()); |
||||||
|
std::for_each_n(upcoming_.begin(), limit, |
||||||
|
[&](const auto i) { ret.push_back(i); }); |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::AddNext(database::TrackId t) -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
if (!current_) { |
||||||
|
current_ = t; |
||||||
|
} else { |
||||||
|
upcoming_.push_front(t); |
||||||
|
} |
||||||
|
|
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::AddNext(const std::vector<database::TrackId>& t) -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
std::for_each(t.rbegin(), t.rend(), |
||||||
|
[&](const auto i) { upcoming_.push_front(i); }); |
||||||
|
if (!current_) { |
||||||
|
current_ = upcoming_.front(); |
||||||
|
upcoming_.pop_front(); |
||||||
|
} |
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::AddLast(database::TrackId t) -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
if (!current_) { |
||||||
|
current_ = t; |
||||||
|
} else { |
||||||
|
upcoming_.push_back(t); |
||||||
|
} |
||||||
|
|
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::AddLast(const std::vector<database::TrackId>& t) -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
std::for_each(t.begin(), t.end(), |
||||||
|
[&](const auto i) { upcoming_.push_back(i); }); |
||||||
|
if (!current_) { |
||||||
|
current_ = upcoming_.front(); |
||||||
|
upcoming_.pop_front(); |
||||||
|
} |
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::Next() -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
if (current_) { |
||||||
|
played_.push_front(*current_); |
||||||
|
} |
||||||
|
if (!upcoming_.empty()) { |
||||||
|
current_ = upcoming_.front(); |
||||||
|
upcoming_.pop_front(); |
||||||
|
} else { |
||||||
|
current_.reset(); |
||||||
|
} |
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::Previous() -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
if (current_) { |
||||||
|
upcoming_.push_front(*current_); |
||||||
|
} |
||||||
|
if (!played_.empty()) { |
||||||
|
current_ = played_.front(); |
||||||
|
played_.pop_front(); |
||||||
|
} else { |
||||||
|
current_.reset(); |
||||||
|
} |
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::Clear() -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
played_.clear(); |
||||||
|
upcoming_.clear(); |
||||||
|
current_.reset(); |
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
auto TrackQueue::RemoveUpcoming(database::TrackId t) -> void { |
||||||
|
const std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
for (auto it = upcoming_.begin(); it != upcoming_.end(); it++) { |
||||||
|
if (*it == t) { |
||||||
|
upcoming_.erase(it); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
events::Dispatch<QueueUpdate, AudioState, ui::UiState>({}); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace audio
|
@ -0,0 +1,62 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <memory> |
||||||
|
#include <utility> |
||||||
|
|
||||||
|
#include "database.hpp" |
||||||
|
|
||||||
|
namespace database { |
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility to simplify waiting for a std::future to complete without blocking. |
||||||
|
* Each instance is good for a single future, and does not directly own anything |
||||||
|
* other than the future itself. |
||||||
|
*/ |
||||||
|
template <typename T> |
||||||
|
class FutureFetcher { |
||||||
|
public: |
||||||
|
explicit FutureFetcher(std::future<T>&& fut) |
||||||
|
: is_consumed_(false), fut_(std::move(fut)) {} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns whether or not the underlying future is still awaiting async work. |
||||||
|
*/ |
||||||
|
auto Finished() -> bool { |
||||||
|
if (!fut_.valid()) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (fut_.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the result of the future, and releases ownership of the underling |
||||||
|
* resource. Will return an absent value if the future became invalid (e.g. |
||||||
|
* the promise associated with it was destroyed.) |
||||||
|
*/ |
||||||
|
auto Result() -> std::optional<T> { |
||||||
|
assert(!is_consumed_); |
||||||
|
if (is_consumed_) { |
||||||
|
return {}; |
||||||
|
} |
||||||
|
is_consumed_ = true; |
||||||
|
if (!fut_.valid()) { |
||||||
|
return {}; |
||||||
|
} |
||||||
|
return fut_.get(); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
bool is_consumed_; |
||||||
|
std::future<T> fut_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace database
|
Loading…
Reference in new issue