diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-07-07 15:29:47 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-07-07 15:29:47 +1000 |
| commit | 39f7545cd5ef7a30bbd482f3579df7744c6b688d (patch) | |
| tree | a760a50cc17365fbcd69eb89ca627ad7feb8c0b6 /src/audio | |
| parent | 2f16d230025c3173cfbecc58b38d6a52b6b0f5f2 (diff) | |
| download | tangara-fw-39f7545cd5ef7a30bbd482f3579df7744c6b688d.tar.gz | |
wire up the playing screen with some real data
Includes implementing song duration calculation for CBR MP3 files
Diffstat (limited to 'src/audio')
| -rw-r--r-- | src/audio/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/audio/audio_decoder.cpp | 14 | ||||
| -rw-r--r-- | src/audio/audio_fsm.cpp | 82 | ||||
| -rw-r--r-- | src/audio/audio_task.cpp | 14 | ||||
| -rw-r--r-- | src/audio/fatfs_audio_input.cpp | 20 | ||||
| -rw-r--r-- | src/audio/include/audio_decoder.hpp | 3 | ||||
| -rw-r--r-- | src/audio/include/audio_events.hpp | 21 | ||||
| -rw-r--r-- | src/audio/include/audio_fsm.hpp | 33 | ||||
| -rw-r--r-- | src/audio/include/fatfs_audio_input.hpp | 2 | ||||
| -rw-r--r-- | src/audio/include/stream_info.hpp | 9 | ||||
| -rw-r--r-- | src/audio/include/track_queue.hpp | 85 | ||||
| -rw-r--r-- | src/audio/stream_info.cpp | 4 | ||||
| -rw-r--r-- | src/audio/track_queue.cpp | 128 |
13 files changed, 343 insertions, 74 deletions
diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 2e085306..38e367aa 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -4,7 +4,7 @@ idf_component_register( SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" - "stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.cpp" + "stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.cpp" "track_queue.cpp" "stream_event.cpp" "pipeline.cpp" "stream_info.cpp" "audio_fsm.cpp" INCLUDE_DIRS "include" REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" "database" "system_fsm") diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index 966a8c37..b8054574 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -53,7 +53,7 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool { } const auto& new_format = std::get<StreamInfo::Encoded>(info.format); - current_input_format_ = info.format; + current_input_format_ = new_format; ESP_LOGI(kTag, "creating new decoder"); auto result = codecs::CreateCodecForType(new_format.type); @@ -112,6 +112,15 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs, .sample_rate = format.sample_rate_hz, }; + if (format.duration_seconds) { + duration_seconds_from_decoder_ = format.duration_seconds; + } else if (format.bits_per_second && + current_input_format_->duration_bytes) { + duration_seconds_from_decoder_ = + (current_input_format_->duration_bytes.value() - res.first) * 8 / + format.bits_per_second.value() / format.num_channels; + } + if (info.seek_to_seconds) { seek_to_sample_ = *info.seek_to_seconds * format.sample_rate_hz; } else { @@ -144,6 +153,9 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs, if (!has_prepared_output_ && !output->prepare(*current_output_format_)) { return; } + if (duration_seconds_from_decoder_) { + output->set_duration(*duration_seconds_from_decoder_); + } has_prepared_output_ = true; // Parse frames and produce samples. diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index 805dffc4..ef33d583 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -19,6 +19,7 @@ #include "pipeline.hpp" #include "system_events.hpp" #include "track.hpp" +#include "track_queue.hpp" namespace audio { @@ -32,11 +33,13 @@ std::unique_ptr<FatfsAudioInput> AudioState::sFileSource; std::unique_ptr<I2SAudioOutput> AudioState::sI2SOutput; std::vector<std::unique_ptr<IAudioElement>> AudioState::sPipeline; -std::deque<AudioState::EnqueuedItem> AudioState::sTrackQueue; +TrackQueue* AudioState::sTrackQueue; auto AudioState::Init(drivers::IGpios* gpio_expander, - std::weak_ptr<database::Database> database) -> bool { + std::weak_ptr<database::Database> database, + TrackQueue* queue) -> bool { sIGpios = gpio_expander; + sTrackQueue = queue; auto dac = drivers::I2SDac::create(gpio_expander); if (!dac) { @@ -94,26 +97,23 @@ void Uninitialised::react(const system_fsm::BootComplete&) { transit<Standby>(); } -void Standby::react(const InputFileOpened& ev) { +void Standby::react(const internal::InputFileOpened& ev) { transit<Playback>(); } -void Standby::react(const PlayTrack& ev) { +void Standby::react(const QueueUpdate& ev) { + auto current_track = sTrackQueue->GetCurrent(); + if (!current_track) { + return; + } + auto db = sDatabase.lock(); if (!db) { ESP_LOGW(kTag, "database not open; ignoring play request"); return; } - if (ev.data) { - sFileSource->OpenFile(ev.data->filepath()); - } else { - sFileSource->OpenFile(db->GetTrackPath(ev.id)); - } -} - -void Standby::react(const PlayFile& ev) { - sFileSource->OpenFile(ev.filename); + sFileSource->OpenFile(db->GetTrackPath(*current_track)); } void Playback::entry() { @@ -126,42 +126,50 @@ void Playback::exit() { sI2SOutput->SetInUse(false); } -void Playback::react(const PlayTrack& ev) { - sTrackQueue.push_back(EnqueuedItem(ev.id)); -} +void Playback::react(const QueueUpdate& ev) { + auto current_track = sTrackQueue->GetCurrent(); + if (!current_track) { + // TODO: return to standby? + return; + } -void Playback::react(const PlayFile& ev) { - sTrackQueue.push_back(EnqueuedItem(ev.filename)); + auto db = sDatabase.lock(); + if (!db) { + return; + } + + // TODO: what if we just finished this, and are preemptively loading the next + // one? + sFileSource->OpenFile(db->GetTrackPath(*current_track)); } void Playback::react(const PlaybackUpdate& ev) { - ESP_LOGI(kTag, "elapsed: %lu", ev.seconds_elapsed); + // ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed, + // ev.seconds_total); } -void Playback::react(const InputFileOpened& ev) {} +void Playback::react(const internal::InputFileOpened& ev) {} -void Playback::react(const InputFileFinished& ev) { - ESP_LOGI(kTag, "finished file"); - if (sTrackQueue.empty()) { +void Playback::react(const internal::InputFileClosed& ev) { + ESP_LOGI(kTag, "finished reading file"); + auto upcoming = sTrackQueue->GetUpcoming(1); + if (upcoming.empty()) { return; } - EnqueuedItem next_item = sTrackQueue.front(); - sTrackQueue.pop_front(); - - if (std::holds_alternative<std::string>(next_item)) { - sFileSource->OpenFile(std::get<std::string>(next_item)); - } else if (std::holds_alternative<database::TrackId>(next_item)) { - auto db = sDatabase.lock(); - if (!db) { - ESP_LOGW(kTag, "database not open; ignoring play request"); - return; - } - sFileSource->OpenFile( - db->GetTrackPath(std::get<database::TrackId>(next_item))); + auto db = sDatabase.lock(); + if (!db) { + return; } + ESP_LOGI(kTag, "preemptively opening next file"); + sFileSource->OpenFile(db->GetTrackPath(upcoming.front())); +} + +void Playback::react(const internal::InputFileFinished& ev) { + ESP_LOGI(kTag, "finished playing file"); + sTrackQueue->Next(); } -void Playback::react(const AudioPipelineIdle& ev) { +void Playback::react(const internal::AudioPipelineIdle& ev) { transit<Standby>(); } diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp index 24bc7be7..babe6849 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_task.cpp @@ -37,6 +37,7 @@ #include "stream_message.hpp" #include "sys/_stdint.h" #include "tasks.hpp" +#include "ui_fsm.hpp" namespace audio { @@ -87,7 +88,7 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) { } if (previously_had_work && !has_work) { - events::Dispatch<AudioPipelineIdle, AudioState>({}); + events::Dispatch<internal::AudioPipelineIdle, AudioState>({}); } previously_had_work = has_work; @@ -136,6 +137,10 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) { if (sink_stream.is_producer_finished()) { sink_stream.mark_consumer_finished(); + if (current_second > 0 || current_sample_in_second > 0) { + events::Dispatch<internal::InputFileFinished, AudioState>({}); + } + current_second = 0; previous_second = 0; current_sample_in_second = 0; @@ -185,8 +190,11 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) { current_sample_in_second -= pcm.sample_rate; } if (previous_second != current_second) { - events::Dispatch<PlaybackUpdate, AudioState>( - {.seconds_elapsed = current_second}); + events::Dispatch<PlaybackUpdate, AudioState, ui::UiState>({ + .seconds_elapsed = current_second, + .seconds_total = + sink_stream.info().duration_seconds.value_or(current_second), + }); } previous_second = current_second; } diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index 894ac842..da605a40 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -56,6 +56,7 @@ auto FatfsAudioInput::OpenFile(std::future<std::optional<std::string>>&& path) } auto FatfsAudioInput::OpenFile(const std::string& path) -> bool { + current_path_.reset(); if (is_file_open_) { f_close(¤t_file_); is_file_open_ = false; @@ -68,6 +69,11 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool { ESP_LOGI(kTag, "opening file %s", path.c_str()); + FILINFO info; + if (f_stat(path.c_str(), &info) != FR_OK) { + ESP_LOGE(kTag, "failed to stat file"); + } + database::TagParserImpl tag_parser; database::TrackTags tags; if (!tag_parser.ReadAndParseTags(path, &tags)) { @@ -95,7 +101,10 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool { .sample_rate = static_cast<uint32_t>(*tags.sample_rate), }; } else { - current_format_ = StreamInfo::Encoded{*stream_type}; + current_format_ = StreamInfo::Encoded{ + .type = *stream_type, + .duration_bytes = info.fsize, + }; } FRESULT res = f_open(¤t_file_, path.c_str(), FA_READ); @@ -104,7 +113,8 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool { return false; } - events::Dispatch<InputFileOpened, AudioState>({}); + events::Dispatch<internal::InputFileOpened, AudioState>({}); + current_path_ = path; is_file_open_ = true; return true; } @@ -124,9 +134,10 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs, if (pending_path_->wait_for(std::chrono::seconds(0)) == std::future_status::ready) { auto result = pending_path_->get(); - if (result) { + if (result && result != current_path_) { OpenFile(*result); } + pending_path_ = {}; } } } @@ -173,10 +184,11 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs, f_close(¤t_file_); is_file_open_ = false; + current_path_.reset(); has_prepared_output_ = false; output->mark_producer_finished(); - events::Dispatch<InputFileFinished, AudioState>({}); + events::Dispatch<internal::InputFileClosed, AudioState>({}); } } diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp index aa051685..a6b4754a 100644 --- a/src/audio/include/audio_decoder.hpp +++ b/src/audio/include/audio_decoder.hpp @@ -40,8 +40,9 @@ class AudioDecoder : public IAudioElement { private: std::unique_ptr<codecs::ICodec> current_codec_; - std::optional<StreamInfo::Format> current_input_format_; + std::optional<StreamInfo::Encoded> current_input_format_; std::optional<StreamInfo::Format> current_output_format_; + std::optional<std::size_t> duration_seconds_from_decoder_; std::optional<std::size_t> seek_to_sample_; bool has_prepared_output_; bool has_samples_to_send_; diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index 019b65a2..8af3703a 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -13,26 +13,31 @@ #include "tinyfsm.hpp" #include "track.hpp" +#include "track_queue.hpp" namespace audio { -struct PlayFile : tinyfsm::Event { - std::string filename; -}; - -struct PlayTrack : tinyfsm::Event { - database::TrackId id; - std::optional<database::TrackData> data; +struct PlaybackStarted : tinyfsm::Event { + database::Track track; }; struct PlaybackUpdate : tinyfsm::Event { uint32_t seconds_elapsed; + uint32_t seconds_total; }; +struct QueueUpdate : tinyfsm::Event {}; + +struct VolumeChanged : tinyfsm::Event {}; + +namespace internal { + struct InputFileOpened : tinyfsm::Event {}; +struct InputFileClosed : tinyfsm::Event {}; struct InputFileFinished : tinyfsm::Event {}; + struct AudioPipelineIdle : tinyfsm::Event {}; -struct VolumeChanged : tinyfsm::Event {}; +} // namespace internal } // namespace audio diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index dadbd072..7910f4e2 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -22,13 +22,15 @@ #include "track.hpp" #include "system_events.hpp" +#include "track_queue.hpp" namespace audio { class AudioState : public tinyfsm::Fsm<AudioState> { public: static auto Init(drivers::IGpios* gpio_expander, - std::weak_ptr<database::Database>) -> bool; + std::weak_ptr<database::Database>, + TrackQueue* queue) -> bool; virtual ~AudioState() {} @@ -45,14 +47,14 @@ class AudioState : public tinyfsm::Fsm<AudioState> { void react(const system_fsm::HasPhonesChanged&); virtual void react(const system_fsm::BootComplete&) {} - virtual void react(const PlayTrack&) {} - virtual void react(const PlayFile&) {} + virtual void react(const QueueUpdate&) {} virtual void react(const PlaybackUpdate&) {} - virtual void react(const InputFileOpened&) {} - virtual void react(const InputFileFinished&) {} - virtual void react(const AudioPipelineIdle&) {} + virtual void react(const internal::InputFileOpened&) {} + virtual void react(const internal::InputFileClosed&) {} + virtual void react(const internal::InputFileFinished&) {} + virtual void react(const internal::AudioPipelineIdle&) {} protected: static drivers::IGpios* sIGpios; @@ -63,8 +65,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> { static std::unique_ptr<I2SAudioOutput> sI2SOutput; static std::vector<std::unique_ptr<IAudioElement>> sPipeline; - typedef std::variant<database::TrackId, std::string> EnqueuedItem; - static std::deque<EnqueuedItem> sTrackQueue; + static TrackQueue* sTrackQueue; }; namespace states { @@ -77,9 +78,8 @@ class Uninitialised : public AudioState { class Standby : public AudioState { public: - void react(const InputFileOpened&) override; - void react(const PlayTrack&) override; - void react(const PlayFile&) override; + void react(const internal::InputFileOpened&) override; + void react(const QueueUpdate&) override; using AudioState::react; }; @@ -89,14 +89,13 @@ class Playback : public AudioState { void entry() override; void exit() override; - void react(const PlayTrack&) override; - void react(const PlayFile&) override; - + void react(const QueueUpdate&) override; void react(const PlaybackUpdate&) override; - void react(const InputFileOpened&) override; - void react(const InputFileFinished&) override; - void react(const AudioPipelineIdle&) override; + void react(const internal::InputFileOpened&) override; + void react(const internal::InputFileClosed&) override; + void react(const internal::InputFileFinished&) override; + void react(const internal::AudioPipelineIdle&) override; using AudioState::react; }; diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp index 77d3b96d..56f92fcf 100644 --- a/src/audio/include/fatfs_audio_input.hpp +++ b/src/audio/include/fatfs_audio_input.hpp @@ -34,6 +34,7 @@ class FatfsAudioInput : public IAudioElement { FatfsAudioInput(); ~FatfsAudioInput(); + auto CurrentFile() -> std::optional<std::string> { return current_path_; } auto OpenFile(std::future<std::optional<std::string>>&& path) -> void; auto OpenFile(const std::string& path) -> bool; @@ -50,6 +51,7 @@ class FatfsAudioInput : public IAudioElement { -> std::optional<codecs::StreamType>; std::optional<std::future<std::optional<std::string>>> pending_path_; + std::optional<std::string> current_path_; FIL current_file_; bool is_file_open_; bool has_prepared_output_; diff --git a/src/audio/include/stream_info.hpp b/src/audio/include/stream_info.hpp index 4db3e5fd..69bf3c4b 100644 --- a/src/audio/include/stream_info.hpp +++ b/src/audio/include/stream_info.hpp @@ -30,13 +30,16 @@ struct StreamInfo { bool is_consumer_finished = true; - // - std::optional<uint32_t> seek_to_seconds{}; + std::optional<std::uint32_t> duration_seconds; + + std::optional<std::uint32_t> seek_to_seconds{}; struct Encoded { // The codec that this stream is associated with. codecs::StreamType type; + std::optional<std::size_t> duration_bytes; + bool operator==(const Encoded&) const = default; }; @@ -95,6 +98,8 @@ class OutputStream { bool prepare(const StreamInfo::Format& new_format); + void set_duration(std::size_t); + const StreamInfo& info() const; cpp::span<std::byte> data() const; diff --git a/src/audio/include/track_queue.hpp b/src/audio/include/track_queue.hpp new file mode 100644 index 00000000..840d71ee --- /dev/null +++ b/src/audio/include/track_queue.hpp @@ -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 diff --git a/src/audio/stream_info.cpp b/src/audio/stream_info.cpp index 748cb9ef..3927e5f8 100644 --- a/src/audio/stream_info.cpp +++ b/src/audio/stream_info.cpp @@ -64,6 +64,10 @@ bool OutputStream::prepare(const StreamInfo::Format& new_format) { return false; } +void OutputStream::set_duration(std::size_t seconds) { + raw_->info->duration_seconds = seconds; +} + const StreamInfo& OutputStream::info() const { return *raw_->info; } diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp new file mode 100644 index 00000000..1c233f8f --- /dev/null +++ b/src/audio/track_queue.cpp @@ -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 |
