Support playing tracks by track id
This commit is contained in:
@@ -89,9 +89,11 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
|
||||
// Check the input stream's format has changed (or, by extension, if this is
|
||||
// the first stream).
|
||||
if (!current_input_format_ || *current_input_format_ != info.format) {
|
||||
ESP_LOGI(kTag, "beginning new stream");
|
||||
has_samples_to_send_ = false;
|
||||
ProcessStreamInfo(info);
|
||||
if (!ProcessStreamInfo(info)) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(kTag, "beginning new stream");
|
||||
auto res = current_codec_->BeginStream(input->data());
|
||||
input->consume(res.first);
|
||||
if (res.second.has_error()) {
|
||||
|
||||
+43
-3
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "audio_fsm.hpp"
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include "audio_decoder.hpp"
|
||||
@@ -14,6 +15,7 @@
|
||||
#include "i2s_audio_output.hpp"
|
||||
#include "i2s_dac.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "track.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
@@ -59,16 +61,36 @@ auto AudioState::Init(drivers::GpioExpander* gpio_expander,
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioState::react(const system_fsm::StorageMounted& ev) {
|
||||
sDatabase = ev.db;
|
||||
}
|
||||
|
||||
namespace states {
|
||||
|
||||
void Uninitialised::react(const system_fsm::BootComplete&) {
|
||||
transit<Standby>();
|
||||
}
|
||||
|
||||
void Standby::react(const PlayFile& ev) {
|
||||
if (sFileSource->OpenFile(ev.filename)) {
|
||||
transit<Playback>();
|
||||
void Standby::react(const InputFileOpened& ev) {
|
||||
transit<Playback>();
|
||||
}
|
||||
|
||||
void Standby::react(const PlayTrack& ev) {
|
||||
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);
|
||||
}
|
||||
|
||||
void Playback::entry() {
|
||||
@@ -81,6 +103,16 @@ void Playback::exit() {
|
||||
sI2SOutput->SetInUse(false);
|
||||
}
|
||||
|
||||
void Playback::react(const PlayTrack& ev) {
|
||||
sTrackQueue.push_back(EnqueuedItem(ev.id));
|
||||
}
|
||||
|
||||
void Playback::react(const PlayFile& ev) {
|
||||
sTrackQueue.push_back(EnqueuedItem(ev.filename));
|
||||
}
|
||||
|
||||
void Playback::react(const InputFileOpened& ev) {}
|
||||
|
||||
void Playback::react(const InputFileFinished& ev) {
|
||||
ESP_LOGI(kTag, "finished file");
|
||||
if (sTrackQueue.empty()) {
|
||||
@@ -91,6 +123,14 @@ void Playback::react(const InputFileFinished& ev) {
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
@@ -38,6 +40,7 @@ namespace audio {
|
||||
|
||||
FatfsAudioInput::FatfsAudioInput()
|
||||
: IAudioElement(),
|
||||
pending_path_(),
|
||||
current_file_(),
|
||||
is_file_open_(false),
|
||||
current_container_(),
|
||||
@@ -45,11 +48,19 @@ FatfsAudioInput::FatfsAudioInput()
|
||||
|
||||
FatfsAudioInput::~FatfsAudioInput() {}
|
||||
|
||||
auto FatfsAudioInput::OpenFile(std::future<std::optional<std::string>>&& path)
|
||||
-> void {
|
||||
pending_path_ = std::move(path);
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
|
||||
if (is_file_open_) {
|
||||
f_close(¤t_file_);
|
||||
is_file_open_ = false;
|
||||
}
|
||||
if (pending_path_) {
|
||||
pending_path_ = {};
|
||||
}
|
||||
ESP_LOGI(kTag, "opening file %s", path.c_str());
|
||||
|
||||
database::TagParserImpl tag_parser;
|
||||
@@ -89,16 +100,33 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
events::Dispatch<InputFileOpened, AudioState>({});
|
||||
is_file_open_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::NeedsToProcess() const -> bool {
|
||||
return is_file_open_;
|
||||
return is_file_open_ || pending_path_;
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
|
||||
OutputStream* output) -> void {
|
||||
if (pending_path_) {
|
||||
ESP_LOGI(kTag, "waiting for path");
|
||||
if (!pending_path_->valid()) {
|
||||
pending_path_ = {};
|
||||
} else {
|
||||
if (pending_path_->wait_for(std::chrono::seconds(0)) ==
|
||||
std::future_status::ready) {
|
||||
ESP_LOGI(kTag, "path ready!");
|
||||
auto result = pending_path_->get();
|
||||
if (result) {
|
||||
OpenFile(*result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_file_open_) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ struct PlayFile : tinyfsm::Event {
|
||||
struct PlayTrack : tinyfsm::Event {
|
||||
database::TrackId id;
|
||||
std::optional<database::TrackData> data;
|
||||
std::optional<database::TrackTags> tags;
|
||||
};
|
||||
|
||||
struct InputFileOpened : tinyfsm::Event {};
|
||||
struct InputFileFinished : tinyfsm::Event {};
|
||||
struct AudioPipelineIdle : tinyfsm::Event {};
|
||||
|
||||
|
||||
@@ -38,10 +38,13 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
|
||||
/* Fallback event handler. Does nothing. */
|
||||
void react(const tinyfsm::Event& ev) {}
|
||||
|
||||
void react(const system_fsm::StorageMounted&);
|
||||
|
||||
virtual void react(const system_fsm::BootComplete&) {}
|
||||
virtual void react(const PlayTrack&) {}
|
||||
virtual void react(const PlayFile&) {}
|
||||
|
||||
virtual void react(const InputFileOpened&) {}
|
||||
virtual void react(const InputFileFinished&) {}
|
||||
virtual void react(const AudioPipelineIdle&) {}
|
||||
|
||||
@@ -69,8 +72,10 @@ class Uninitialised : public AudioState {
|
||||
|
||||
class Standby : public AudioState {
|
||||
public:
|
||||
void react(const PlayTrack&) override {}
|
||||
void react(const InputFileOpened&) override;
|
||||
void react(const PlayTrack&) override;
|
||||
void react(const PlayFile&) override;
|
||||
|
||||
using AudioState::react;
|
||||
};
|
||||
|
||||
@@ -79,6 +84,10 @@ class Playback : public AudioState {
|
||||
void entry() override;
|
||||
void exit() override;
|
||||
|
||||
void react(const PlayTrack&) override;
|
||||
void react(const PlayFile&) override;
|
||||
|
||||
void react(const InputFileOpened&) override;
|
||||
void react(const InputFileFinished&) override;
|
||||
void react(const AudioPipelineIdle&) override;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -33,6 +34,7 @@ class FatfsAudioInput : public IAudioElement {
|
||||
FatfsAudioInput();
|
||||
~FatfsAudioInput();
|
||||
|
||||
auto OpenFile(std::future<std::optional<std::string>>&& path) -> void;
|
||||
auto OpenFile(const std::string& path) -> bool;
|
||||
|
||||
auto NeedsToProcess() const -> bool override;
|
||||
@@ -47,6 +49,7 @@ class FatfsAudioInput : public IAudioElement {
|
||||
auto ContainerToStreamType(database::Encoding)
|
||||
-> std::optional<codecs::StreamType>;
|
||||
|
||||
std::optional<std::future<std::optional<std::string>>> pending_path_;
|
||||
FIL current_file_;
|
||||
bool is_file_open_;
|
||||
|
||||
|
||||
@@ -229,6 +229,18 @@ auto Database::Update() -> std::future<void> {
|
||||
});
|
||||
}
|
||||
|
||||
auto Database::GetTrackPath(TrackId id)
|
||||
-> std::future<std::optional<std::string>> {
|
||||
return worker_task_->Dispatch<std::optional<std::string>>(
|
||||
[=, this]() -> std::optional<std::string> {
|
||||
auto track_data = dbGetTrackData(id);
|
||||
if (track_data) {
|
||||
return track_data->filepath();
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
auto Database::GetTracks(std::size_t page_size) -> std::future<Result<Track>*> {
|
||||
return worker_task_->Dispatch<Result<Track>*>([=, this]() -> Result<Track>* {
|
||||
Continuation<Track> c{.iterator = nullptr,
|
||||
|
||||
@@ -82,6 +82,8 @@ class Database {
|
||||
|
||||
auto Update() -> std::future<void>;
|
||||
|
||||
auto GetTrackPath(TrackId id) -> std::future<std::optional<std::string>>;
|
||||
|
||||
auto GetTracks(std::size_t page_size) -> std::future<Result<Track>*>;
|
||||
auto GetDump(std::size_t page_size) -> std::future<Result<std::string>*>;
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "database.hpp"
|
||||
#include "tinyfsm.hpp"
|
||||
|
||||
namespace system_fsm {
|
||||
@@ -38,7 +41,9 @@ struct StorageUnmountRequested : tinyfsm::Event {};
|
||||
/*
|
||||
* Sent by SysState when the system storage has been successfully mounted.
|
||||
*/
|
||||
struct StorageMounted : tinyfsm::Event {};
|
||||
struct StorageMounted : tinyfsm::Event {
|
||||
std::weak_ptr<database::Database> db;
|
||||
};
|
||||
|
||||
struct StorageError : tinyfsm::Event {};
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ void Running::entry() {
|
||||
|
||||
ESP_LOGI(kTag, "storage loaded okay");
|
||||
events::Dispatch<StorageMounted, SystemState, audio::AudioState, ui::UiState>(
|
||||
StorageMounted());
|
||||
StorageMounted{.db = sDatabase});
|
||||
}
|
||||
|
||||
void Running::exit() {
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ auto AllocateStack() -> cpp::span<StackType_t>;
|
||||
// amount of stack space.
|
||||
template <>
|
||||
auto AllocateStack<Type::kAudio>() -> cpp::span<StackType_t> {
|
||||
std::size_t size = 32 * 1024;
|
||||
std::size_t size = 48 * 1024;
|
||||
return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_DEFAULT)),
|
||||
size};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user