diff options
Diffstat (limited to 'src/audio')
| -rw-r--r-- | src/audio/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/audio/audio_fsm.cpp | 73 | ||||
| -rw-r--r-- | src/audio/include/audio_events.hpp | 2 | ||||
| -rw-r--r-- | src/audio/include/audio_fsm.hpp | 12 | ||||
| -rw-r--r-- | src/audio/include/track_queue.hpp | 3 | ||||
| -rw-r--r-- | src/audio/track_queue.cpp | 71 |
6 files changed, 143 insertions, 20 deletions
diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index b219ab6e..8ed5efbb 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -9,6 +9,6 @@ idf_component_register( "audio_source.cpp" INCLUDE_DIRS "include" REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" - "database" "system_fsm" "speexdsp" "millershuffle") + "database" "system_fsm" "speexdsp" "millershuffle" "libcppbor") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index 560e655a..e37c887b 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -49,6 +49,7 @@ std::shared_ptr<BluetoothAudioOutput> AudioState::sBtOutput; std::shared_ptr<IAudioOutput> AudioState::sOutput; std::optional<database::TrackId> AudioState::sCurrentTrack; +bool AudioState::sIsPlaybackAllowed; void AudioState::react(const system_fsm::KeyLockChanged& ev) { if (ev.locking && sServices) { @@ -138,6 +139,23 @@ auto AudioState::playTrack(database::TrackId id) -> void { }); } +auto AudioState::readyToPlay() -> bool { + return sCurrentTrack.has_value() && sIsPlaybackAllowed; +} + +void AudioState::react(const TogglePlayPause& ev) { + sIsPlaybackAllowed = !sIsPlaybackAllowed; + if (readyToPlay()) { + if (!is_in_state<states::Playback>()) { + transit<states::Playback>(); + } + } else { + if (!is_in_state<states::Standby>()) { + transit<states::Standby>(); + } + } +} + namespace states { void Uninitialised::react(const system_fsm::BootComplete& ev) { @@ -199,21 +217,55 @@ void Playback::react(const PlayFile& ev) { } void Standby::react(const internal::InputFileOpened& ev) { - transit<Playback>(); + if (readyToPlay()) { + transit<Playback>(); + } } void Standby::react(const QueueUpdate& ev) { auto current_track = sServices->track_queue().current(); - if (!current_track || (sCurrentTrack && *sCurrentTrack == *current_track)) { + if (!current_track || (sCurrentTrack && (*sCurrentTrack == *current_track))) { return; } playTrack(*current_track); } -void Standby::react(const TogglePlayPause& ev) { - if (sCurrentTrack) { - transit<Playback>(); +static const char kQueueKey[] = "audio:queue"; + +void Standby::react(const system_fsm::KeyLockChanged& ev) { + if (!ev.locking) { + return; } + sServices->bg_worker().Dispatch<void>([]() { + auto db = sServices->database().lock(); + if (!db) { + return; + } + auto& queue = sServices->track_queue(); + if (queue.totalSize() <= queue.currentPosition()) { + // Nothing is playing, so don't bother saving the queue. + db->put(kQueueKey, ""); + return; + } + db->put(kQueueKey, queue.serialise()); + }); +} + +void Standby::react(const system_fsm::StorageMounted& ev) { + sServices->bg_worker().Dispatch<void>([]() { + auto db = sServices->database().lock(); + if (!db) { + return; + } + auto res = db->get(kQueueKey); + if (res) { + // Don't restore the same queue again. This ideally should do nothing, + // but guards against bad edge cases where restoring the queue ends up + // causing a crash. + db->put(kQueueKey, ""); + sServices->track_queue().deserialise(*res); + } + }); } void Playback::entry() { @@ -226,17 +278,14 @@ void Playback::entry() { void Playback::exit() { ESP_LOGI(kTag, "finishing playback"); - // TODO(jacqueline): Second case where it's useful to wait for the i2s buffer - // to drain. - vTaskDelay(pdMS_TO_TICKS(10)); sOutput->SetMode(IAudioOutput::Modes::kOnPaused); // Stash the current volume now, in case it changed during playback, since we // might be powering off soon. sServices->nvs().AmpCurrentVolume(sOutput->GetVolume()); - events::System().Dispatch(PlaybackFinished{}); - events::Ui().Dispatch(PlaybackFinished{}); + events::System().Dispatch(PlaybackStopped{}); + events::Ui().Dispatch(PlaybackStopped{}); } void Playback::react(const system_fsm::HasPhonesChanged& ev) { @@ -259,10 +308,6 @@ void Playback::react(const QueueUpdate& ev) { playTrack(*current_track); } -void Playback::react(const TogglePlayPause& ev) { - transit<Standby>(); -} - void Playback::react(const PlaybackUpdate& ev) { ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed, ev.track->duration); diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index b76d8c89..03584062 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -35,7 +35,7 @@ struct PlaybackUpdate : tinyfsm::Event { std::shared_ptr<Track> track; }; -struct PlaybackFinished : tinyfsm::Event {}; +struct PlaybackStopped : tinyfsm::Event {}; struct QueueUpdate : tinyfsm::Event { bool current_changed; diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index b8c505b0..884af8a8 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -52,12 +52,13 @@ class AudioState : public tinyfsm::Fsm<AudioState> { void react(const OutputModeChanged&); virtual void react(const system_fsm::BootComplete&) {} - void react(const system_fsm::KeyLockChanged&); + virtual void react(const system_fsm::KeyLockChanged&); + virtual void react(const system_fsm::StorageMounted&) {} virtual void react(const PlayFile&) {} virtual void react(const QueueUpdate&) {} virtual void react(const PlaybackUpdate&) {} - virtual void react(const TogglePlayPause&) {} + void react(const TogglePlayPause&); virtual void react(const internal::InputFileOpened&) {} virtual void react(const internal::InputFileClosed&) {} @@ -77,6 +78,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> { static std::shared_ptr<IAudioOutput> sOutput; static std::optional<database::TrackId> sCurrentTrack; + + auto readyToPlay() -> bool; + static bool sIsPlaybackAllowed; }; namespace states { @@ -92,7 +96,8 @@ class Standby : public AudioState { void react(const PlayFile&) override; void react(const internal::InputFileOpened&) override; void react(const QueueUpdate&) override; - void react(const TogglePlayPause&) override; + void react(const system_fsm::KeyLockChanged&) override; + void react(const system_fsm::StorageMounted&) override; using AudioState::react; }; @@ -107,7 +112,6 @@ class Playback : public AudioState { void react(const PlayFile&) override; void react(const QueueUpdate&) override; void react(const PlaybackUpdate&) override; - void react(const TogglePlayPause&) override; void react(const internal::InputFileOpened&) override; void react(const internal::InputFileClosed&) override; diff --git a/src/audio/include/track_queue.hpp b/src/audio/include/track_queue.hpp index 0ff72021..5b14fd4a 100644 --- a/src/audio/include/track_queue.hpp +++ b/src/audio/include/track_queue.hpp @@ -98,6 +98,9 @@ class TrackQueue { auto repeat(bool) -> void; auto repeat() const -> bool; + auto serialise() -> std::string; + auto deserialise(const std::string&) -> void; + // Cannot be copied or moved. TrackQueue(const TrackQueue&) = delete; TrackQueue& operator=(const TrackQueue&) = delete; diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp index 33858e0a..d68f2821 100644 --- a/src/audio/track_queue.cpp +++ b/src/audio/track_queue.cpp @@ -5,6 +5,7 @@ */ #include "track_queue.hpp" +#include <stdint.h> #include <algorithm> #include <cstdint> @@ -310,4 +311,74 @@ auto TrackQueue::repeat() const -> bool { return repeat_; } +auto TrackQueue::serialise() -> std::string { + cppbor::Array tracks{}; + for (database::TrackId track : tracks_) { + tracks.add(cppbor::Uint(track)); + } + // FIXME: this should include the RandomIterator's seed as well. + cppbor::Array encoded{ + cppbor::Uint{pos_}, + std::move(tracks), + }; + return encoded.toString(); +} + +class QueueParseClient : public cppbor::ParseClient { + public: + QueueParseClient(size_t& pos, std::pmr::vector<database::TrackId>& tracks) + : pos_(pos), + tracks_(tracks), + in_root_array_(false), + in_track_list_(false) {} + + ParseClient* item(std::unique_ptr<cppbor::Item>& item, + const uint8_t* hdrBegin, + const uint8_t* valueBegin, + const uint8_t* end) override { + if (item->type() == cppbor::ARRAY) { + if (!in_root_array_) { + in_root_array_ = true; + } else { + in_track_list_ = true; + } + } else if (item->type() == cppbor::UINT) { + auto val = item->asUint()->unsignedValue(); + if (in_track_list_) { + tracks_.push_back(val); + } else { + pos_ = static_cast<size_t>(val); + } + } + return this; + } + + ParseClient* itemEnd(std::unique_ptr<cppbor::Item>& item, + const uint8_t* hdrBegin, + const uint8_t* valueBegin, + const uint8_t* end) override { + return this; + } + + void error(const uint8_t* position, + const std::string& errorMessage) override {} + + private: + size_t& pos_; + std::pmr::vector<database::TrackId>& tracks_; + + bool in_root_array_; + bool in_track_list_; +}; + +auto TrackQueue::deserialise(const std::string& s) -> void { + if (s.empty()) { + return; + } + QueueParseClient client{pos_, tracks_}; + const uint8_t* data = reinterpret_cast<const uint8_t*>(s.data()); + cppbor::parse(data, data + s.size(), &client); + notifyChanged(true); +} + } // namespace audio |
