summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/fatfs/src/ffconf.h2
-rw-r--r--src/tangara/audio/audio_fsm.cpp20
-rw-r--r--src/tangara/audio/playlist.cpp154
-rw-r--r--src/tangara/audio/playlist.hpp56
-rw-r--r--src/tangara/audio/track_queue.cpp158
-rw-r--r--src/tangara/audio/track_queue.hpp24
-rw-r--r--src/tangara/system_fsm/booting.cpp2
-rw-r--r--src/tangara/test/CMakeLists.txt2
-rw-r--r--src/tangara/test/audio/test_playlist.cpp86
-rw-r--r--src/tangara/ui/ui_fsm.cpp6
-rw-r--r--test/sdkconfig.test1
11 files changed, 377 insertions, 134 deletions
diff --git a/lib/fatfs/src/ffconf.h b/lib/fatfs/src/ffconf.h
index 77bd5981..a5a4ae17 100644
--- a/lib/fatfs/src/ffconf.h
+++ b/lib/fatfs/src/ffconf.h
@@ -58,7 +58,7 @@
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
-#define FF_USE_STRFUNC 0
+#define FF_USE_STRFUNC 1
#define FF_PRINT_LLI 0
#define FF_PRINT_FLOAT 0
#define FF_STRF_ENCODE 3
diff --git a/src/tangara/audio/audio_fsm.cpp b/src/tangara/audio/audio_fsm.cpp
index fbc38f97..65261d75 100644
--- a/src/tangara/audio/audio_fsm.cpp
+++ b/src/tangara/audio/audio_fsm.cpp
@@ -96,9 +96,7 @@ void AudioState::react(const QueueUpdate& ev) {
};
auto current = sServices->track_queue().current();
- if (current) {
- cmd.new_track = *current;
- }
+ cmd.new_track = current;
switch (ev.reason) {
case QueueUpdate::kExplicitUpdate:
@@ -176,18 +174,21 @@ void AudioState::react(const internal::DecodingFinished& ev) {
sServices->bg_worker().Dispatch<void>([=]() {
auto& queue = sServices->track_queue();
auto current = queue.current();
- if (!current) {
+ if (std::holds_alternative<std::monostate>(current)) {
return;
}
auto db = sServices->database().lock();
if (!db) {
return;
}
- auto path = db->getTrackPath(*current);
- if (!path) {
- return;
+ std::string path;
+ if (std::holds_alternative<std::string>(current)) {
+ path = std::get<std::string>(current);
+ } else if (std::holds_alternative<database::TrackId>(current)) {
+ auto tid = std::get<database::TrackId>(current);
+ path = db->getTrackPath(tid).value_or("");
}
- if (*path == ev.track->uri) {
+ if (path == ev.track->uri) {
queue.finish();
}
});
@@ -449,6 +450,9 @@ void Standby::react(const system_fsm::SdStateChanged& ev) {
return;
}
+ // Open the queue file
+ sServices->track_queue().open();
+
// Restore the currently playing file before restoring the queue. This way,
// we can fall back to restarting the queue's current track if there's any
// issue restoring the current file.
diff --git a/src/tangara/audio/playlist.cpp b/src/tangara/audio/playlist.cpp
new file mode 100644
index 00000000..850b7335
--- /dev/null
+++ b/src/tangara/audio/playlist.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2024 ailurux <ailuruxx@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#include "playlist.hpp"
+
+#include <string.h>
+
+#include "audio/playlist.hpp"
+#include "database/database.hpp"
+#include "esp_log.h"
+#include "ff.h"
+
+namespace audio {
+[[maybe_unused]] static constexpr char kTag[] = "playlist";
+
+Playlist::Playlist(std::string playlistFilepath)
+ : filepath_(playlistFilepath), mutex_(), total_size_(0), pos_(0) {}
+
+auto Playlist::open() -> bool {
+ FRESULT res =
+ f_open(&file_, filepath_.c_str(), FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "failed to open file! res: %i", res);
+ return false;
+ }
+ // Count all entries
+ consumeAndCount(-1);
+ // Grab the first one
+ skipTo(0);
+ return true;
+}
+
+Playlist::~Playlist() {
+ f_close(&file_);
+}
+
+auto Playlist::currentPosition() const -> size_t {
+ return pos_;
+}
+
+auto Playlist::size() const -> size_t {
+ return total_size_;
+}
+
+auto Playlist::append(Item i) -> void {
+ std::unique_lock<std::mutex> lock(mutex_);
+ auto offset = f_tell(&file_);
+ // Seek to end and append
+ auto res = f_lseek(&file_, f_size(&file_));
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "Seek to end of file failed? Error %d", res);
+ return;
+ }
+ // TODO: Resolve paths for track id, etc
+ std::string path;
+ if (std::holds_alternative<std::string>(i)) {
+ path = std::get<std::string>(i);
+ f_printf(&file_, "%s\n", path.c_str());
+ total_size_++;
+ if (current_value_.empty()) {
+ current_value_ = path;
+ }
+ }
+ // Restore position
+ res = f_lseek(&file_, offset);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "Failed to restore file position after append?");
+ return;
+ }
+ res = f_sync(&file_);
+ if (res != FR_OK) {
+ ESP_LOGE(kTag, "Failed to sync playlist file after append");
+ return;
+ }
+}
+
+auto Playlist::skipTo(size_t position) -> void {
+ pos_ = position;
+ consumeAndCount(position);
+}
+
+auto Playlist::next() -> void {
+ if (!atEnd()) {
+ pos_++;
+ skipTo(pos_);
+ }
+}
+
+auto Playlist::prev() -> void {
+ // Naive approach to see how that goes for now
+ pos_--;
+ skipTo(pos_);
+}
+
+auto Playlist::value() const -> std::string {
+ return current_value_;
+}
+
+auto Playlist::clear() -> bool {
+ auto res = f_close(&file_);
+ if (res != FR_OK) {
+ return false;
+ }
+ res =
+ f_open(&file_, filepath_.c_str(), FA_READ | FA_WRITE | FA_CREATE_ALWAYS);
+ if (res != FR_OK) {
+ return false;
+ }
+ total_size_ = 0;
+ current_value_.clear();
+ pos_ = 0;
+ return true;
+}
+
+auto Playlist::atEnd() -> bool {
+ return pos_ + 1 >= total_size_;
+}
+
+auto Playlist::filepath() -> std::string {
+ return filepath_;
+}
+
+auto Playlist::consumeAndCount(ssize_t upto) -> bool {
+ std::unique_lock<std::mutex> lock(mutex_);
+ TCHAR buff[512];
+ size_t count = 0;
+ f_rewind(&file_);
+ while (!f_eof(&file_)) {
+ // TODO: Correctly handle lines longer than this
+ // TODO: Also correctly handle the case where the last entry doesn't end in
+ // \n
+ auto res = f_gets(buff, 512, &file_);
+ if (res == NULL) {
+ ESP_LOGW(kTag, "Error consuming playlist file at line %d", count);
+ return false;
+ }
+ count++;
+
+ if (upto >= 0 && count > upto) {
+ size_t len = strlen(buff);
+ current_value_.assign(buff, len - 1);
+ break;
+ }
+ }
+ if (upto < 0) {
+ total_size_ = count;
+ f_rewind(&file_);
+ }
+ return true;
+}
+
+} // namespace audio \ No newline at end of file
diff --git a/src/tangara/audio/playlist.hpp b/src/tangara/audio/playlist.hpp
new file mode 100644
index 00000000..b278d8a6
--- /dev/null
+++ b/src/tangara/audio/playlist.hpp
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright 2024 ailurux <ailuruxx@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+#include <string>
+#include <variant>
+#include "database/database.hpp"
+#include "database/track.hpp"
+#include "ff.h"
+
+namespace audio {
+
+/*
+ * Owns and manages a playlist file.
+ * Each line in the playlist file is the absolute filepath of the track to play.
+ * In order to avoid mapping to byte offsets, each line must contain only a
+ * filepath (ie, no comments are supported). This limitation may be removed
+ * later if benchmarks show that the file can be quickly scanned from 'bookmark'
+ * offsets. This is a subset of the m3u format and ideally will be
+ * import/exportable to and from this format, to better support playlists from
+ * beets import and other music management software.
+ */
+class Playlist {
+ public:
+ Playlist(std::string playlistFilepath);
+ ~Playlist();
+ using Item =
+ std::variant<database::TrackId, database::TrackIterator, std::string>;
+ auto open() -> bool;
+ auto currentPosition() const -> size_t;
+ auto size() const -> size_t;
+ auto append(Item i) -> void;
+ auto skipTo(size_t position) -> void;
+ auto next() -> void;
+ auto prev() -> void;
+ auto value() const -> std::string;
+ auto clear() -> bool;
+ auto atEnd() -> bool;
+ auto filepath() -> std::string;
+
+ private:
+ std::string filepath_;
+ std::mutex mutex_;
+ size_t total_size_;
+ size_t pos_;
+ FIL file_;
+ std::string current_value_;
+
+ auto consumeAndCount(ssize_t upto) -> bool;
+};
+
+} // namespace audio \ No newline at end of file
diff --git a/src/tangara/audio/track_queue.cpp b/src/tangara/audio/track_queue.cpp
index 603b0de1..1689f06a 100644
--- a/src/tangara/audio/track_queue.cpp
+++ b/src/tangara/audio/track_queue.cpp
@@ -28,6 +28,7 @@
#include "memory_resource.hpp"
#include "tasks.hpp"
#include "ui/ui_fsm.hpp"
+#include "track_queue.hpp"
namespace audio {
@@ -83,65 +84,67 @@ auto notifyChanged(bool current_changed, Reason reason) -> void {
events::Audio().Dispatch(ev);
}
-TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker)
+TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db)
: mutex_(),
bg_worker_(bg_worker),
- pos_(0),
- tracks_(&memory::kSpiRamResource),
+ db_(db),
+ playlist_("queue.playlist"), // TODO
shuffle_(),
repeat_(false),
replay_(false) {}
-auto TrackQueue::current() const -> std::optional<database::TrackId> {
+auto TrackQueue::current() const -> TrackItem {
const std::shared_lock<std::shared_mutex> lock(mutex_);
- if (pos_ >= tracks_.size()) {
+ std::string val = playlist_.value();
+ if (val.empty()) {
return {};
}
- return tracks_[pos_];
+ return val;
}
-auto TrackQueue::peekNext(std::size_t limit) const
- -> std::vector<database::TrackId> {
+auto TrackQueue::currentPosition() const -> size_t {
const std::shared_lock<std::shared_mutex> lock(mutex_);
- std::vector<database::TrackId> out;
- for (size_t i = pos_ + 1; i < pos_ + limit + 1 && i < tracks_.size(); i++) {
- out.push_back(i);
- }
- return out;
+ return playlist_.currentPosition();
}
-auto TrackQueue::peekPrevious(std::size_t limit) const
- -> std::vector<database::TrackId> {
+auto TrackQueue::totalSize() const -> size_t {
const std::shared_lock<std::shared_mutex> lock(mutex_);
- std::vector<database::TrackId> out;
- for (size_t i = pos_ - 1; i < pos_ - limit - 1 && i >= tracks_.size(); i--) {
- out.push_back(i);
- }
- return out;
+ return playlist_.size();
}
-auto TrackQueue::currentPosition() const -> size_t {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- return pos_;
+auto TrackQueue::open() -> bool {
+ // FIX ME: If playlist opening fails, should probably fall back to a vector of tracks or something
+ // so that we're not necessarily always needing mounted storage
+ return playlist_.open();
}
-auto TrackQueue::totalSize() const -> size_t {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- return tracks_.size();
+auto TrackQueue::getFilepath(database::TrackId id) -> std::optional<std::string> {
+ auto db = db_.lock();
+ if (!db) {
+ return {};
+ }
+ return db->getTrackPath(id);
}
+
+// TODO WIP: Atm only appends are allowed, this will only ever append regardless of what index
+// is given. But it is kept like this for compatability for now.
auto TrackQueue::insert(Item i, size_t index) -> void {
+ append(i);
+}
+
+auto TrackQueue::append(Item i) -> void {
bool was_queue_empty;
bool current_changed;
{
const std::shared_lock<std::shared_mutex> lock(mutex_);
- was_queue_empty = pos_ == tracks_.size();
- current_changed = was_queue_empty || index == pos_;
+ was_queue_empty = playlist_.currentPosition() >= playlist_.size();
+ current_changed = was_queue_empty; // Dont support inserts yet
}
auto update_shuffler = [=, this]() {
if (shuffle_) {
- shuffle_->resize(tracks_.size());
+ shuffle_->resize(playlist_.size());
// If there wasn't anything already playing, then we should make sure we
// begin playback at a random point, instead of always starting with
// whatever was inserted first and *then* shuffling.
@@ -149,7 +152,7 @@ auto TrackQueue::insert(Item i, size_t index) -> void {
// 'play this track now' (by inserting at the current pos) to work even
// when shuffling is enabled.
if (was_queue_empty) {
- pos_ = shuffle_->current();
+ playlist_.skipTo(shuffle_->current());
}
}
};
@@ -157,10 +160,11 @@ auto TrackQueue::insert(Item i, size_t index) -> void {
if (std::holds_alternative<database::TrackId>(i)) {
{
const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (index <= tracks_.size()) {
- tracks_.insert(tracks_.begin() + index, std::get<database::TrackId>(i));
- update_shuffler();
+ auto filename = getFilepath(std::get<database::TrackId>(i));
+ if (filename) {
+ playlist_.append(*filename);
}
+ update_shuffler();
}
notifyChanged(current_changed, Reason::kExplicitUpdate);
} else if (std::holds_alternative<database::TrackIterator>(i)) {
@@ -169,7 +173,6 @@ auto TrackQueue::insert(Item i, size_t index) -> void {
// doesn't block.
bg_worker_.Dispatch<void>([=, this]() {
database::TrackIterator it = std::get<database::TrackIterator>(i);
- size_t working_pos = index;
while (true) {
auto next = *it;
if (!next) {
@@ -179,11 +182,11 @@ auto TrackQueue::insert(Item i, size_t index) -> void {
// like current().
{
const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (working_pos <= tracks_.size()) {
- tracks_.insert(tracks_.begin() + working_pos, *next);
+ auto filename = *getFilepath(*next);
+ if (!filename.empty()) {
+ playlist_.append(filename);
}
}
- working_pos++;
it++;
}
{
@@ -195,15 +198,6 @@ auto TrackQueue::insert(Item i, size_t index) -> void {
}
}
-auto TrackQueue::append(Item i) -> void {
- size_t end;
- {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- end = tracks_.size();
- }
- insert(i, end);
-}
-
auto TrackQueue::next() -> void {
next(Reason::kExplicitUpdate);
}
@@ -215,17 +209,16 @@ auto TrackQueue::next(Reason r) -> void {
const std::unique_lock<std::shared_mutex> lock(mutex_);
if (shuffle_) {
shuffle_->next();
- pos_ = shuffle_->current();
+ playlist_.skipTo(shuffle_->current());
} else {
- if (pos_ + 1 >= tracks_.size()) {
+ if (playlist_.atEnd()) {
if (replay_) {
- pos_ = 0;
+ playlist_.skipTo(0);
} else {
- pos_ = tracks_.size();
changed = false;
}
} else {
- pos_++;
+ playlist_.next();
}
}
}
@@ -240,16 +233,16 @@ auto TrackQueue::previous() -> void {
const std::unique_lock<std::shared_mutex> lock(mutex_);
if (shuffle_) {
shuffle_->prev();
- pos_ = shuffle_->current();
+ playlist_.skipTo(shuffle_->current());
} else {
- if (pos_ == 0) {
+ if (playlist_.currentPosition() == 0) {
if (repeat_) {
- pos_ = tracks_.size() - 1;
+ playlist_.skipTo(playlist_.size()-1);
} else {
changed = false;
}
} else {
- pos_--;
+ playlist_.prev();
}
}
}
@@ -265,39 +258,10 @@ auto TrackQueue::finish() -> void {
}
}
-auto TrackQueue::skipTo(database::TrackId id) -> void {
- // Defer this work to the background not because it's particularly
- // long-running (although it could be), but because we want to ensure we
- // only search for the given id after any previously pending iterator
- // insertions have finished.
- bg_worker_.Dispatch<void>([=, this]() {
- bool found = false;
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- for (size_t i = 0; i < tracks_.size(); i++) {
- if (tracks_[i] == id) {
- pos_ = i;
- found = true;
- break;
- }
- }
- }
- if (found) {
- notifyChanged(true, Reason::kExplicitUpdate);
- }
- });
-}
-
auto TrackQueue::clear() -> void {
{
const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (tracks_.empty()) {
- return;
- }
-
- pos_ = 0;
- tracks_.clear();
-
+ playlist_.clear();
if (shuffle_) {
shuffle_->resize(0);
}
@@ -309,10 +273,8 @@ auto TrackQueue::clear() -> void {
auto TrackQueue::random(bool en) -> void {
{
const std::unique_lock<std::shared_mutex> lock(mutex_);
- // Don't check for en == true already; this has the side effect that
- // repeated calls with en == true will re-shuffle.
if (en) {
- shuffle_.emplace(tracks_.size());
+ shuffle_.emplace(playlist_.size());
shuffle_->replay(replay_);
} else {
shuffle_.reset();
@@ -360,14 +322,11 @@ auto TrackQueue::replay() const -> bool {
auto TrackQueue::serialise() -> std::string {
cppbor::Array tracks{};
- for (database::TrackId track : tracks_) {
- tracks.add(cppbor::Uint(track));
- }
cppbor::Map encoded;
encoded.add(cppbor::Uint{0}, cppbor::Array{
- cppbor::Uint{pos_},
cppbor::Bool{repeat_},
cppbor::Bool{replay_},
+ cppbor::Uint{playlist_.currentPosition()},
});
if (shuffle_) {
encoded.add(cppbor::Uint{1}, cppbor::Array{
@@ -376,7 +335,6 @@ auto TrackQueue::serialise() -> std::string {
cppbor::Uint{shuffle_->pos()},
});
}
- encoded.add(cppbor::Uint{2}, std::move(tracks));
return encoded.toString();
}
@@ -401,9 +359,6 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item(
case 1:
state_ = State::kShuffle;
break;
- case 2:
- state_ = State::kTracks;
- break;
default:
state_ = State::kFinished;
}
@@ -412,7 +367,8 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item(
if (item->type() == cppbor::ARRAY) {
i_ = 0;
} else if (item->type() == cppbor::UINT) {
- queue_.pos_ = item->asUint()->unsignedValue();
+ auto val = item->asUint()->unsignedValue();
+ queue_.playlist_.skipTo(val);
} else if (item->type() == cppbor::SIMPLE) {
bool val = item->asBool()->value();
if (i_ == 0) {
@@ -444,10 +400,6 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item(
}
i_++;
}
- } else if (state_ == State::kTracks) {
- if (item->type() == cppbor::UINT) {
- queue_.tracks_.push_back(item->asUint()->unsignedValue());
- }
} else if (state_ == State::kFinished) {
}
return this;
@@ -470,10 +422,6 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::itemEnd(
if (item->type() == cppbor::ARRAY) {
state_ = State::kRoot;
}
- } else if (state_ == State::kTracks) {
- if (item->type() == cppbor::ARRAY) {
- state_ = State::kRoot;
- }
} else if (state_ == State::kFinished) {
}
return this;
diff --git a/src/tangara/audio/track_queue.hpp b/src/tangara/audio/track_queue.hpp
index 427d5f75..6f50f162 100644
--- a/src/tangara/audio/track_queue.hpp
+++ b/src/tangara/audio/track_queue.hpp
@@ -17,6 +17,7 @@
#include "database/database.hpp"
#include "database/track.hpp"
#include "tasks.hpp"
+#include "playlist.hpp"
namespace audio {
@@ -64,22 +65,15 @@ class RandomIterator {
*/
class TrackQueue {
public:
- TrackQueue(tasks::WorkerPool& bg_worker);
+ TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db);
/* Returns the currently playing track. */
- auto current() const -> std::optional<database::TrackId>;
-
- /* Returns, in order, tracks that have been queued to be played next. */
- auto peekNext(std::size_t limit) const -> std::vector<database::TrackId>;
-
- /*
- * Returns the tracks in the queue that have already been played, ordered
- * most recently played first.
- */
- auto peekPrevious(std::size_t limit) const -> std::vector<database::TrackId>;
+ using TrackItem = std::variant<std::string, database::TrackId, std::monostate>;
+ auto current() const -> TrackItem;
auto currentPosition() const -> size_t;
auto totalSize() const -> size_t;
+ auto open() -> bool;
using Item = std::variant<database::TrackId, database::TrackIterator>;
auto insert(Item, size_t index = 0) -> void;
@@ -97,8 +91,6 @@ class TrackQueue {
*/
auto finish() -> void;
- auto skipTo(database::TrackId) -> void;
-
/*
* Removes all tracks from all queues, and stops any currently playing track.
*/
@@ -122,13 +114,14 @@ class TrackQueue {
private:
auto next(QueueUpdate::Reason r) -> void;
+ auto getFilepath(database::TrackId id) -> std::optional<std::string>;
mutable std::shared_mutex mutex_;
tasks::WorkerPool& bg_worker_;
+ database::Handle db_;
- size_t pos_;
- std::pmr::vector<database::TrackId> tracks_;
+ Playlist playlist_;
std::optional<RandomIterator> shuffle_;
bool repeat_;
@@ -159,7 +152,6 @@ class TrackQueue {
kRoot,
kMetadata,
kShuffle,
- kTracks,
kFinished,
};
State state_;
diff --git a/src/tangara/system_fsm/booting.cpp b/src/tangara/system_fsm/booting.cpp
index 86993767..a3fed9fa 100644
--- a/src/tangara/system_fsm/booting.cpp
+++ b/src/tangara/system_fsm/booting.cpp
@@ -97,7 +97,7 @@ auto Booting::entry() -> void {
sServices->samd(), std::unique_ptr<drivers::AdcBattery>(adc)));
sServices->track_queue(
- std::make_unique<audio::TrackQueue>(sServices->bg_worker()));
+ std::make_unique<audio::TrackQueue>(sServices->bg_worker(), sServices->database()));
sServices->tag_parser(std::make_unique<database::TagParserImpl>());
sServices->collator(locale::CreateCollator());
sServices->tts(std::make_unique<tts::Provider>());
diff --git a/src/tangara/test/CMakeLists.txt b/src/tangara/test/CMakeLists.txt
index 728c06b0..58882f9f 100644
--- a/src/tangara/test/CMakeLists.txt
+++ b/src/tangara/test/CMakeLists.txt
@@ -3,5 +3,5 @@
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
- SRC_DIRS "battery"
+ SRC_DIRS "battery" "audio"
INCLUDE_DIRS "." REQUIRES catch2 cmock tangara fixtures)
diff --git a/src/tangara/test/audio/test_playlist.cpp b/src/tangara/test/audio/test_playlist.cpp
new file mode 100644
index 00000000..147b3ac0
--- /dev/null
+++ b/src/tangara/test/audio/test_playlist.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2023 ailurux <ailuruxx@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "audio/playlist.hpp"
+
+#include <dirent.h>
+
+#include <cstdio>
+#include <fstream>
+#include <iostream>
+
+#include "catch2/catch.hpp"
+
+#include "drivers/gpios.hpp"
+#include "drivers/i2c.hpp"
+#include "drivers/storage.hpp"
+#include "drivers/spi.hpp"
+#include "i2c_fixture.hpp"
+#include "spi_fixture.hpp"
+#include "ff.h"
+
+namespace audio {
+
+static const std::string kTestFilename = "test_playlist2.m3u";
+static const std::string kTestFilePath = kTestFilename;
+
+TEST_CASE("playlist file", "[integration]") {
+ I2CFixture i2c;
+ SpiFixture spi;
+ std::unique_ptr<drivers::IGpios> gpios{drivers::Gpios::Create(false)};
+
+ if (gpios->Get(drivers::IGpios::Pin::kSdCardDetect)) {
+ // Skip if nothing is inserted.
+ SKIP("no sd card detected; skipping storage tests");
+ return;
+ }
+
+ {
+ std::unique_ptr<drivers::SdStorage> result(drivers::SdStorage::Create(*gpios).value());
+ Playlist plist(kTestFilePath);
+ REQUIRE(plist.clear());
+
+ SECTION("write to the playlist file") {
+ plist.append("test1.mp3");
+ plist.append("test2.mp3");
+ plist.append("test3.mp3");
+ plist.append("test4.wav");
+ plist.append("directory/test1.mp3");
+ plist.append("directory/test2.mp3");
+ plist.append("a/really/long/directory/test1.mp3");
+ plist.append("directory/and/another/test2.mp3");
+ REQUIRE(plist.size() == 8);
+
+ SECTION("read from the playlist file") {
+ Playlist plist2(kTestFilePath);
+ REQUIRE(plist2.size() == 8);
+ REQUIRE(plist2.value() == "test1.mp3");
+ plist2.next();
+ REQUIRE(plist2.value() == "test2.mp3");
+ plist2.prev();
+ REQUIRE(plist2.value() == "test1.mp3");
+ }
+ }
+
+ BENCHMARK("appending item") {
+ plist.append("A/New/Item.wav");
+ };
+
+ BENCHMARK("opening playlist file") {
+ Playlist plist2(kTestFilePath);
+ REQUIRE(plist2.size() > 100);
+ return plist2.size();
+ };
+
+ BENCHMARK("opening playlist file and appending entry") {
+ Playlist plist2(kTestFilePath);
+ REQUIRE(plist2.size() > 100);
+ plist2.append("A/Nother/New/Item.opus");
+ return plist2.size();
+ };
+ }
+}
+} // namespace audio
diff --git a/src/tangara/ui/ui_fsm.cpp b/src/tangara/ui/ui_fsm.cpp
index c5ede83c..476732db 100644
--- a/src/tangara/ui/ui_fsm.cpp
+++ b/src/tangara/ui/ui_fsm.cpp
@@ -398,10 +398,12 @@ void UiState::react(const system_fsm::BatteryStateChanged& ev) {
void UiState::react(const audio::QueueUpdate&) {
auto& queue = sServices->track_queue();
- sQueueSize.setDirect(static_cast<int>(queue.totalSize()));
+ auto queue_size = queue.totalSize();
+ sQueueSize.setDirect(static_cast<int>(queue_size));
int current_pos = queue.currentPosition();
- if (queue.current()) {
+ // If there is nothing in the queue, the position should be 0, otherwise, add one because lua
+ if (queue_size > 0) {
current_pos++;
}
sQueuePosition.setDirect(current_pos);
diff --git a/test/sdkconfig.test b/test/sdkconfig.test
index c0fbf922..10d9595c 100644
--- a/test/sdkconfig.test
+++ b/test/sdkconfig.test
@@ -11,3 +11,4 @@ CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_ESP_TASK_WDT=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="../partitions.csv"
+CONFIG_ESP_TASK_WDT_EN=n