diff options
Diffstat (limited to 'src/ui')
| -rw-r--r-- | src/ui/include/screen.hpp | 10 | ||||
| -rw-r--r-- | src/ui/include/screen_playing.hpp | 35 | ||||
| -rw-r--r-- | src/ui/include/ui_fsm.hpp | 24 | ||||
| -rw-r--r-- | src/ui/screen_playing.cpp | 95 | ||||
| -rw-r--r-- | src/ui/ui_fsm.cpp | 49 |
5 files changed, 178 insertions, 35 deletions
diff --git a/src/ui/include/screen.hpp b/src/ui/include/screen.hpp index 7ff06fbd..13b92a09 100644 --- a/src/ui/include/screen.hpp +++ b/src/ui/include/screen.hpp @@ -15,14 +15,24 @@ namespace ui { +/* + * Base class for ever discrete screen in the app. Provides a consistent + * interface that can be used for transitioning between screens, adding them to + * back stacks, etc. + */ class Screen { public: Screen() : root_(lv_obj_create(NULL)), group_(lv_group_create()) {} + virtual ~Screen() { lv_obj_del(root_); lv_group_del(group_); } + /* + * Called periodically to allow the screen to update itself, e.g. to handle + * std::futures that are still loading in. + */ virtual auto Tick() -> void {} auto root() -> lv_obj_t* { return root_; } diff --git a/src/ui/include/screen_playing.hpp b/src/ui/include/screen_playing.hpp index 5ccfe391..148f2774 100644 --- a/src/ui/include/screen_playing.hpp +++ b/src/ui/include/screen_playing.hpp @@ -7,30 +7,54 @@ #pragma once #include <stdint.h> +#include <sys/_stdint.h> #include <memory> #include <vector> #include "lvgl.h" #include "database.hpp" +#include "future_fetcher.hpp" #include "screen.hpp" #include "track.hpp" +#include "track_queue.hpp" namespace ui { namespace screens { +/* + * The 'Now Playing' / 'Currently Playing' screen that contains information + * about the current track, as well as playback controls. + */ class Playing : public Screen { public: - explicit Playing(database::Track t); + explicit Playing(std::weak_ptr<database::Database> db, + audio::TrackQueue* queue); ~Playing(); - auto BindTrack(database::Track t) -> void; + auto Tick() -> void override; - auto UpdateTime(uint32_t) -> void; - auto UpdateNextUp(std::vector<database::Track> tracks) -> void; + // Callbacks invoked by the UI state machine in response to audio events. + + auto OnTrackUpdate() -> void; + auto OnPlaybackUpdate(uint32_t, uint32_t) -> void; + auto OnQueueUpdate() -> void; private: - database::Track track_; + auto BindTrack(const database::Track& track) -> void; + auto ApplyNextUp(const std::vector<database::Track>& tracks) -> void; + + std::weak_ptr<database::Database> db_; + audio::TrackQueue* queue_; + + std::optional<database::Track> track_; + std::vector<database::Track> next_tracks_; + + std::unique_ptr<database::FutureFetcher<std::optional<database::Track>>> + new_track_; + std::unique_ptr< + database::FutureFetcher<std::vector<std::optional<database::Track>>>> + new_next_tracks_; lv_obj_t* artist_label_; lv_obj_t* album_label_; @@ -40,7 +64,6 @@ class Playing : public Screen { lv_obj_t* play_pause_control_; lv_obj_t* next_up_container_; - std::vector<database::Track> next_tracks_; }; } // namespace screens diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index cd1ec492..2fc6db4e 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -19,13 +19,14 @@ #include "storage.hpp" #include "system_events.hpp" #include "touchwheel.hpp" +#include "track_queue.hpp" #include "ui_events.hpp" namespace ui { class UiState : public tinyfsm::Fsm<UiState> { public: - static auto Init(drivers::IGpios* gpio_expander) -> bool; + static auto Init(drivers::IGpios*, audio::TrackQueue*) -> bool; virtual ~UiState() {} @@ -39,12 +40,14 @@ class UiState : public tinyfsm::Fsm<UiState> { /* Fallback event handler. Does nothing. */ void react(const tinyfsm::Event& ev) {} - virtual void react(const audio::PlaybackUpdate){}; + virtual void react(const audio::PlaybackStarted&) {} + virtual void react(const audio::PlaybackUpdate&) {} + virtual void react(const audio::QueueUpdate&) {} - virtual void react(const system_fsm::KeyLockChanged&){}; + virtual void react(const system_fsm::KeyLockChanged&) {} - virtual void react(const internal::RecordSelected&){}; - virtual void react(const internal::IndexSelected&){}; + virtual void react(const internal::RecordSelected&) {} + virtual void react(const internal::IndexSelected&) {} virtual void react(const system_fsm::DisplayReady&) {} virtual void react(const system_fsm::BootComplete&) {} @@ -52,8 +55,11 @@ class UiState : public tinyfsm::Fsm<UiState> { protected: void PushScreen(std::shared_ptr<Screen>); + void PopScreen(); static drivers::IGpios* sIGpios; + static audio::TrackQueue* sQueue; + static std::shared_ptr<drivers::TouchWheel> sTouchWheel; static std::shared_ptr<drivers::RelativeWheel> sRelativeWheel; static std::shared_ptr<drivers::Display> sDisplay; @@ -61,7 +67,6 @@ class UiState : public tinyfsm::Fsm<UiState> { static std::stack<std::shared_ptr<Screen>> sScreens; static std::shared_ptr<Screen> sCurrentScreen; - static std::unique_ptr<screens::Playing> sPlayingScreen; }; namespace states { @@ -81,12 +86,17 @@ class Browse : public UiState { void react(const system_fsm::KeyLockChanged&) override; void react(const system_fsm::StorageMounted&) override; + using UiState::react; }; class Playing : public UiState { void entry() override; + void exit() override; - void react(const audio::PlaybackUpdate) override; + void react(const audio::PlaybackStarted&) override; + void react(const audio::PlaybackUpdate&) override; + void react(const audio::QueueUpdate&) override; + using UiState::react; }; class FatalError : public UiState {}; diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp index 39f6b04d..50b1d33a 100644 --- a/src/ui/screen_playing.cpp +++ b/src/ui/screen_playing.cpp @@ -5,13 +5,17 @@ */ #include "screen_playing.hpp" +#include <sys/_stdint.h> +#include <memory> #include "core/lv_obj.h" #include "core/lv_obj_tree.h" +#include "database.hpp" #include "esp_log.h" #include "extra/layouts/flex/lv_flex.h" #include "extra/layouts/grid/lv_grid.h" #include "font/lv_symbol_def.h" +#include "future_fetcher.hpp" #include "lvgl.h" #include "core/lv_group.h" @@ -20,8 +24,10 @@ #include "extra/widgets/list/lv_list.h" #include "extra/widgets/menu/lv_menu.h" #include "extra/widgets/spinner/lv_spinner.h" +#include "future_fetcher.hpp" #include "hal/lv_hal_disp.h" #include "index.hpp" +#include "misc/lv_anim.h" #include "misc/lv_area.h" #include "misc/lv_color.h" #include "track.hpp" @@ -35,6 +41,8 @@ namespace ui { namespace screens { +static constexpr std::size_t kMaxUpcoming = 10; + static lv_style_t scrubber_style; auto info_label(lv_obj_t* parent) -> lv_obj_t* { @@ -60,7 +68,13 @@ auto next_up_label(lv_obj_t* parent, const std::string& text) -> lv_obj_t* { return label; } -Playing::Playing(database::Track track) : track_(track) { +Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue* queue) + : db_(db), + queue_(queue), + track_(), + next_tracks_(), + new_track_(), + new_next_tracks_() { lv_obj_set_layout(root_, LV_LAYOUT_FLEX); lv_obj_set_size(root_, lv_pct(100), lv_pct(200)); lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN); @@ -131,12 +145,72 @@ Playing::Playing(database::Track track) : track_(track) { lv_obj_set_flex_align(next_up_container_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_END); - BindTrack(track); + OnTrackUpdate(); + OnQueueUpdate(); } Playing::~Playing() {} -auto Playing::BindTrack(database::Track t) -> void { +auto Playing::OnTrackUpdate() -> void { + auto current = queue_->GetCurrent(); + if (!current) { + return; + } + if (track_ && track_->data().id() == *current) { + return; + } + auto db = db_.lock(); + if (!db) { + return; + } + new_track_.reset(new database::FutureFetcher<std::optional<database::Track>>( + db->GetTrack(*current))); +} + +auto Playing::OnPlaybackUpdate(uint32_t pos_seconds, uint32_t new_duration) + -> void { + if (!track_) { + return; + } + lv_bar_set_range(scrubber_, 0, new_duration); + lv_bar_set_value(scrubber_, pos_seconds, LV_ANIM_ON); +} + +auto Playing::OnQueueUpdate() -> void { + auto current = queue_->GetUpcoming(kMaxUpcoming); + auto db = db_.lock(); + if (!db) { + return; + } + new_next_tracks_.reset( + new database::FutureFetcher<std::vector<std::optional<database::Track>>>( + db->GetBulkTracks(current))); +} + +auto Playing::Tick() -> void { + if (new_track_ && new_track_->Finished()) { + auto res = new_track_->Result(); + new_track_.reset(); + if (res && *res) { + BindTrack(**res); + } + } + if (new_next_tracks_ && new_next_tracks_->Finished()) { + auto res = new_next_tracks_->Result(); + new_next_tracks_.reset(); + if (res) { + std::vector<database::Track> filtered; + for (const auto& t : *res) { + if (t) { + filtered.push_back(*t); + } + } + ApplyNextUp(filtered); + } + } +} + +auto Playing::BindTrack(const database::Track& t) -> void { track_ = t; lv_label_set_text(artist_label_, @@ -145,16 +219,12 @@ auto Playing::BindTrack(database::Track t) -> void { t.tags().at(database::Tag::kAlbum).value_or("").c_str()); lv_label_set_text(title_label_, t.TitleOrFilename().c_str()); - // TODO. - lv_bar_set_range(scrubber_, 0, 0); + std::optional<int> duration = t.tags().duration; + lv_bar_set_range(scrubber_, 0, duration.value_or(1)); lv_bar_set_value(scrubber_, 0, LV_ANIM_OFF); } -auto Playing::UpdateTime(uint32_t time) -> void { - lv_bar_set_value(scrubber_, time, LV_ANIM_OFF); -} - -auto Playing::UpdateNextUp(std::vector<database::Track> tracks) -> void { +auto Playing::ApplyNextUp(const std::vector<database::Track>& tracks) -> void { // TODO(jacqueline): Do a proper diff to maintain selection. int children = lv_obj_get_child_cnt(next_up_container_); while (children > 0) { @@ -163,8 +233,9 @@ auto Playing::UpdateNextUp(std::vector<database::Track> tracks) -> void { } next_tracks_ = tracks; - for (const auto &track : next_tracks_) { - lv_group_add_obj(group_, next_up_label(next_up_container_, track.TitleOrFilename())); + for (const auto& track : next_tracks_) { + lv_group_add_obj( + group_, next_up_label(next_up_container_, track.TitleOrFilename())); } } diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 13658c37..5394311c 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -19,6 +19,7 @@ #include "screen_track_browser.hpp" #include "system_events.hpp" #include "touchwheel.hpp" +#include "track_queue.hpp" namespace ui { @@ -27,6 +28,8 @@ static constexpr char kTag[] = "ui_fsm"; static const std::size_t kRecordsPerPage = 10; drivers::IGpios* UiState::sIGpios; +audio::TrackQueue* UiState::sQueue; + std::shared_ptr<drivers::TouchWheel> UiState::sTouchWheel; std::shared_ptr<drivers::RelativeWheel> UiState::sRelativeWheel; std::shared_ptr<drivers::Display> UiState::sDisplay; @@ -34,10 +37,11 @@ std::weak_ptr<database::Database> UiState::sDb; std::stack<std::shared_ptr<Screen>> UiState::sScreens; std::shared_ptr<Screen> UiState::sCurrentScreen; -std::unique_ptr<screens::Playing> UiState::sPlayingScreen; -auto UiState::Init(drivers::IGpios* gpio_expander) -> bool { +auto UiState::Init(drivers::IGpios* gpio_expander, audio::TrackQueue* queue) + -> bool { sIGpios = gpio_expander; + sQueue = queue; lv_init(); sDisplay.reset( @@ -70,6 +74,14 @@ void UiState::PushScreen(std::shared_ptr<Screen> screen) { sCurrentScreen = screen; } +void UiState::PopScreen() { + if (sScreens.empty()) { + return; + } + sCurrentScreen = sScreens.top(); + sScreens.pop(); +} + namespace states { void Splash::exit() { @@ -107,12 +119,9 @@ void Browse::react(const internal::RecordSelected& ev) { if (ev.record.track()) { ESP_LOGI(kTag, "selected track '%s'", ev.record.text()->c_str()); // TODO(jacqueline): We should also send some kind of playlist info here. - auto track = ev.record.track().value(); - events::Dispatch<audio::PlayTrack, audio::AudioState>(audio::PlayTrack{ - .id = track.data().id(), - .data = track.data(), - }); - PushScreen(std::make_shared<screens::Playing>(track)); + sQueue->Clear(); + sQueue->AddLast(ev.record.track()->data().id()); + transit<Playing>(); } else { ESP_LOGI(kTag, "selected record '%s'", ev.record.text()->c_str()); auto cont = ev.record.Expand(kRecordsPerPage); @@ -138,8 +147,28 @@ void Browse::react(const internal::IndexSelected& ev) { std::move(query))); } -void Playing::react(const audio::PlaybackUpdate ev) { - sPlayingScreen->UpdateTime(ev.seconds_elapsed); +static std::shared_ptr<screens::Playing> sPlayingScreen; + +void Playing::entry() { + sPlayingScreen.reset(new screens::Playing(sDb, sQueue)); + PushScreen(sPlayingScreen); +} + +void Playing::exit() { + sPlayingScreen.reset(); + PopScreen(); +} + +void Playing::react(const audio::PlaybackStarted& ev) { + sPlayingScreen->OnTrackUpdate(); +} + +void Playing::react(const audio::PlaybackUpdate& ev) { + sPlayingScreen->OnPlaybackUpdate(ev.seconds_elapsed, ev.seconds_total); +} + +void Playing::react(const audio::QueueUpdate& ev) { + sPlayingScreen->OnQueueUpdate(); } } // namespace states |
