summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-09-19 15:03:43 +1000
committerjacqueline <me@jacqueline.id.au>2024-09-19 15:03:43 +1000
commit9c95c2b4222da1fb31855f3985ab96d90865086d (patch)
tree8a5bdca0fdffa3720cbef7b683295f629ce308da /src
parent111085b857249a9442e118a5e37a1732716da6a2 (diff)
parent1eddfe97d9398215d4512785c669cf7cc94b6223 (diff)
downloadtangara-fw-9c95c2b4222da1fb31855f3985ab96d90865086d.tar.gz
Merge branch 'main' of codeberg.org:cool-tech-zone/tangara-fw
Diffstat (limited to 'src')
-rw-r--r--src/tangara/audio/audio_events.hpp2
-rw-r--r--src/tangara/audio/audio_fsm.cpp35
-rw-r--r--src/tangara/audio/audio_fsm.hpp3
-rw-r--r--src/tangara/audio/track_queue.cpp23
-rw-r--r--src/tangara/audio/track_queue.hpp1
-rw-r--r--src/tangara/database/database.cpp19
-rw-r--r--src/tangara/database/database.hpp5
-rw-r--r--src/tangara/database/records.cpp9
-rw-r--r--src/tangara/database/track.cpp12
-rw-r--r--src/tangara/database/track.hpp7
-rw-r--r--src/tangara/lua/lua_database.cpp72
-rw-r--r--src/tangara/lua/lua_database.hpp2
-rw-r--r--src/tangara/lua/lua_queue.cpp14
-rw-r--r--src/tangara/lua/property.cpp24
14 files changed, 197 insertions, 31 deletions
diff --git a/src/tangara/audio/audio_events.hpp b/src/tangara/audio/audio_events.hpp
index 56d150b2..89dc28ff 100644
--- a/src/tangara/audio/audio_events.hpp
+++ b/src/tangara/audio/audio_events.hpp
@@ -106,6 +106,8 @@ struct QueueUpdate : tinyfsm::Event {
kBulkLoadingUpdate,
};
Reason reason;
+
+ std::optional<uint32_t> seek_to_second;
};
struct StepUpVolume : tinyfsm::Event {};
diff --git a/src/tangara/audio/audio_fsm.cpp b/src/tangara/audio/audio_fsm.cpp
index 1daf568e..f9823fb3 100644
--- a/src/tangara/audio/audio_fsm.cpp
+++ b/src/tangara/audio/audio_fsm.cpp
@@ -78,6 +78,8 @@ StreamCues AudioState::sStreamCues;
bool AudioState::sIsPaused = true;
bool AudioState::sIsTtsPlaying = false;
+uint8_t AudioState::sUpdateCounter = 0;
+
auto AudioState::emitPlaybackUpdate(bool paused) -> void {
std::optional<uint32_t> position;
auto current = sStreamCues.current();
@@ -88,6 +90,16 @@ auto AudioState::emitPlaybackUpdate(bool paused) -> void {
current.first->start_offset.value_or(0);
}
+ // If we've got an elapsed duration and it's more than 5 minutes
+ // increment a counter. Every 60 counts (ie, every minute) save the current elapsed position
+ if (position && *position > (5 * 60)) {
+ sUpdateCounter++;
+ if (sUpdateCounter >= 60) {
+ sUpdateCounter = 0;
+ updateSavedPosition(current.first->uri, *position);
+ }
+ }
+
PlaybackUpdate event{
.current_track = current.first,
.track_position = position,
@@ -101,7 +113,7 @@ auto AudioState::emitPlaybackUpdate(bool paused) -> void {
void AudioState::react(const QueueUpdate& ev) {
SetTrack cmd{
.new_track = std::monostate{},
- .seek_to_second = {},
+ .seek_to_second = ev.seek_to_second,
};
auto current = sServices->track_queue().current();
@@ -387,6 +399,27 @@ void AudioState::react(const OutputModeChanged& ev) {
}
}
+auto AudioState::updateSavedPosition(std::string uri, uint32_t position)
+ -> void {
+ sServices->bg_worker().Dispatch<void>([=]() {
+ auto db = sServices->database().lock();
+ if (!db) {
+ return;
+ }
+ auto id = db->getTrackID(uri);
+ if (!id) {
+ return;
+ }
+ auto track = db->getTrack(*id);
+ if (!track) {
+ return;
+ }
+ auto data = track->data().clone();
+ data->last_position = position;
+ db->setTrackData(*id, *data);
+ });
+}
+
auto AudioState::updateOutputMode() -> void {
if (is_in_state<states::Playback>() || sIsTtsPlaying) {
sOutput->mode(IAudioOutput::Modes::kOnPlaying);
diff --git a/src/tangara/audio/audio_fsm.hpp b/src/tangara/audio/audio_fsm.hpp
index bc3feb55..c765b417 100644
--- a/src/tangara/audio/audio_fsm.hpp
+++ b/src/tangara/audio/audio_fsm.hpp
@@ -75,6 +75,8 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
auto emitPlaybackUpdate(bool paused) -> void;
auto commitVolume() -> void;
+ auto updateSavedPosition(std::string uri, uint32_t position) -> void;
+
static std::shared_ptr<system_fsm::ServiceLocator> sServices;
static std::shared_ptr<FatfsStreamFactory> sStreamFactory;
@@ -90,6 +92,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
static std::optional<IAudioOutput::Format> sDrainFormat;
static bool sIsPaused;
+ static uint8_t sUpdateCounter;
static bool sIsTtsPlaying;
};
diff --git a/src/tangara/audio/track_queue.cpp b/src/tangara/audio/track_queue.cpp
index 2c1faf96..ff24637b 100644
--- a/src/tangara/audio/track_queue.cpp
+++ b/src/tangara/audio/track_queue.cpp
@@ -85,6 +85,16 @@ auto notifyChanged(bool current_changed, Reason reason) -> void {
events::Audio().Dispatch(ev);
}
+auto notifyPlayFrom(uint32_t start_from_position) -> void {
+ QueueUpdate ev{
+ .current_changed = true,
+ .reason = Reason::kExplicitUpdate,
+ .seek_to_second = start_from_position,
+ };
+ events::Ui().Dispatch(ev);
+ events::Audio().Dispatch(ev);
+}
+
TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db)
: mutex_(),
bg_worker_(bg_worker),
@@ -109,6 +119,17 @@ auto TrackQueue::current() const -> TrackItem {
return val;
}
+auto TrackQueue::playFromPosition(const std::string& filepath,
+ uint32_t position) -> void {
+ clear();
+ {
+ const std::unique_lock<std::shared_mutex> lock(mutex_);
+ playlist_.append(filepath);
+ updateShuffler(true);
+ }
+ notifyPlayFrom(position);
+}
+
auto TrackQueue::currentPosition() const -> size_t {
const std::shared_lock<std::shared_mutex> lock(mutex_);
return position_;
@@ -245,7 +266,7 @@ auto TrackQueue::currentPosition(size_t position) -> bool {
goTo(position);
}
- // If we're explicitly setting the position, we want to treat it as though
+ // If we're explicitly setting the position, we want to treat it as though
// the current track has changed, even if the position was the same
notifyChanged(true, Reason::kExplicitUpdate);
return true;
diff --git a/src/tangara/audio/track_queue.hpp b/src/tangara/audio/track_queue.hpp
index a8d1dc3a..727b4be0 100644
--- a/src/tangara/audio/track_queue.hpp
+++ b/src/tangara/audio/track_queue.hpp
@@ -78,6 +78,7 @@ class TrackQueue {
auto open() -> bool;
auto openPlaylist(const std::string& playlist_file, bool notify = true)
-> bool;
+ auto playFromPosition(const std::string& filepath, uint32_t position) -> void;
using Item =
std::variant<database::TrackId, database::TrackIterator, std::string>;
diff --git a/src/tangara/database/database.cpp b/src/tangara/database/database.cpp
index 64451f48..9d0de695 100644
--- a/src/tangara/database/database.cpp
+++ b/src/tangara/database/database.cpp
@@ -44,6 +44,7 @@
#include "memory_resource.hpp"
#include "result.hpp"
#include "tasks.hpp"
+#include "database.hpp"
namespace database {
@@ -269,6 +270,24 @@ auto Database::getTrack(TrackId id) -> std::shared_ptr<Track> {
return std::make_shared<Track>(data, tags);
}
+auto Database::getTrackID(std::string path) -> std::optional<TrackId> {
+ std::string raw_data;
+ if (!db_->Get(leveldb::ReadOptions(), EncodePathKey(path), &raw_data).ok()) {
+ return {};
+ }
+ return BytesToTrackId(raw_data);
+}
+
+auto Database::setTrackData(TrackId id, const TrackData& data) -> void {
+ std::string key = EncodeDataKey(id);
+ std::string raw_val = EncodeDataValue(data);
+
+ auto res = db_->Put(leveldb::WriteOptions(), key, raw_val);
+ if (!res.ok()) {
+ ESP_LOGI(kTag, "Updating track data failed for track ID: %lu", id);
+ }
+}
+
auto Database::getIndexes() -> std::vector<IndexInfo> {
// TODO(jacqueline): This probably needs to be async? When we have runtime
// configurable indexes, they will need to come from somewhere.
diff --git a/src/tangara/database/database.hpp b/src/tangara/database/database.hpp
index 18070353..6dd13b0d 100644
--- a/src/tangara/database/database.hpp
+++ b/src/tangara/database/database.hpp
@@ -37,7 +37,7 @@
namespace database {
-const uint8_t kCurrentDbVersion = 7;
+const uint8_t kCurrentDbVersion = 8;
struct SearchKey;
class Record;
@@ -78,6 +78,9 @@ class Database {
auto getTrackPath(TrackId id) -> std::optional<std::string>;
auto getTrack(TrackId id) -> std::shared_ptr<Track>;
+ auto getTrackID(std::string path) -> std::optional<TrackId>;
+
+ auto setTrackData(TrackId id, const TrackData& data) -> void;
auto getIndexes() -> std::vector<IndexInfo>;
auto updateIndexes() -> void;
diff --git a/src/tangara/database/records.cpp b/src/tangara/database/records.cpp
index 17009cd8..012cda64 100644
--- a/src/tangara/database/records.cpp
+++ b/src/tangara/database/records.cpp
@@ -93,6 +93,7 @@ auto EncodeDataValue(const TrackData& track) -> std::string {
cppbor::Uint{track.modified_at.first},
cppbor::Uint{track.modified_at.second},
tag_hashes,
+ cppbor::Uint{track.last_position},
};
return val.toString();
}
@@ -104,13 +105,14 @@ auto ParseDataValue(const leveldb::Slice& slice) -> std::shared_ptr<TrackData> {
return nullptr;
}
auto vals = item->asArray();
- if (vals->size() != 7 || vals->get(0)->type() != cppbor::UINT ||
+ if (vals->size() != 8 || vals->get(0)->type() != cppbor::UINT ||
vals->get(1)->type() != cppbor::TSTR ||
vals->get(2)->type() != cppbor::UINT ||
vals->get(3)->type() != cppbor::SIMPLE ||
vals->get(4)->type() != cppbor::UINT ||
vals->get(5)->type() != cppbor::UINT ||
- vals->get(6)->type() != cppbor::MAP) {
+ vals->get(6)->type() != cppbor::MAP ||
+ vals->get(7)->type() != cppbor::UINT) {
return {};
}
auto res = std::make_shared<TrackData>();
@@ -127,6 +129,9 @@ auto ParseDataValue(const leveldb::Slice& slice) -> std::shared_ptr<TrackData> {
auto tag = static_cast<Tag>(entry.first->asUint()->unsignedValue());
res->individual_tag_hashes[tag] = entry.second->asUint()->unsignedValue();
}
+
+ res->last_position = vals->get(7)->asUint()->unsignedValue();
+
return res;
}
diff --git a/src/tangara/database/track.cpp b/src/tangara/database/track.cpp
index cdb7543c..e737dd37 100644
--- a/src/tangara/database/track.cpp
+++ b/src/tangara/database/track.cpp
@@ -293,4 +293,16 @@ auto TrackTags::Hash() const -> uint64_t {
return komihash_stream_final(&stream);
}
+auto database::TrackData::clone() const -> std::shared_ptr<TrackData> {
+ auto data = std::make_shared<TrackData>();
+ data->id = id;
+ data->filepath = filepath;
+ data->tags_hash = tags_hash;
+ data->individual_tag_hashes = individual_tag_hashes;
+ data->is_tombstoned = is_tombstoned;
+ data->modified_at = modified_at;
+ data->last_position = last_position;
+ return data;
+}
+
} // namespace database
diff --git a/src/tangara/database/track.hpp b/src/tangara/database/track.hpp
index 6501e31f..03fc47b9 100644
--- a/src/tangara/database/track.hpp
+++ b/src/tangara/database/track.hpp
@@ -159,7 +159,8 @@ struct TrackData {
tags_hash(0),
individual_tag_hashes(&memory::kSpiRamResource),
is_tombstoned(false),
- modified_at() {}
+ modified_at(),
+ last_position(0) {}
TrackId id;
std::pmr::string filepath;
@@ -167,9 +168,11 @@ struct TrackData {
std::pmr::unordered_map<Tag, uint64_t> individual_tag_hashes;
bool is_tombstoned;
std::pair<uint16_t, uint16_t> modified_at;
+ uint32_t last_position;
- TrackData(TrackData&& other) = delete;
+ TrackData(const TrackData&& other) = delete;
TrackData& operator=(TrackData& other) = delete;
+ auto clone() const -> std::shared_ptr<TrackData>;
bool operator==(const TrackData&) const = default;
};
diff --git a/src/tangara/lua/lua_database.cpp b/src/tangara/lua/lua_database.cpp
index bf84a399..e184265a 100644
--- a/src/tangara/lua/lua_database.cpp
+++ b/src/tangara/lua/lua_database.cpp
@@ -73,6 +73,56 @@ static auto indexes(lua_State* state) -> int {
return 1;
}
+auto pushTagValue(lua_State* L, const database::TagValue& val) -> void {
+ std::visit(
+ [&](auto&& arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, std::pmr::string>) {
+ lua_pushlstring(L, arg.data(), arg.size());
+ } else if constexpr (std::is_same_v<
+ T, std::span<const std::pmr::string>>) {
+ lua_createtable(L, 0, arg.size());
+ for (const auto& i : arg) {
+ lua_pushlstring(L, i.data(), i.size());
+ lua_pushboolean(L, true);
+ lua_rawset(L, -3);
+ }
+ } else if constexpr (std::is_same_v<T, uint32_t>) {
+ lua_pushinteger(L, arg);
+ } else {
+ lua_pushnil(L);
+ }
+ },
+ val);
+}
+
+static void pushTrack(lua_State* L, const database::Track& track) {
+ lua_newtable(L);
+
+ lua_pushliteral(L, "tags");
+ lua_newtable(L);
+ for (const auto& tag : track.tags().allPresent()) {
+ lua_pushstring(L, database::tagName(tag).c_str());
+ pushTagValue(L, track.tags().get(tag));
+ lua_settable(L, -3);
+ }
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "id");
+ lua_pushinteger(L, track.data().id);
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "filepath");
+ lua_pushstring(L, track.data().filepath.c_str());
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "saved_position");
+ lua_pushinteger(L, track.data().last_position);
+ lua_settable(L, -3);
+
+
+}
+
static auto version(lua_State* L) -> int {
Bridge* instance = Bridge::Get(L);
auto db = instance->services().database().lock();
@@ -111,9 +161,29 @@ static auto update(lua_State* L) -> int {
return 0;
}
+static auto track_by_id(lua_State* L) -> int {
+ auto id = luaL_checkinteger(L, -1);
+
+ Bridge* instance = Bridge::Get(L);
+ auto db = instance->services().database().lock();
+ if (!db) {
+ return 0;
+ }
+
+ auto track = db->getTrack(id);
+ if (!track) {
+ return 0;
+ }
+
+ pushTrack(L, *track);
+
+ return 1;
+}
+
static const struct luaL_Reg kDatabaseFuncs[] = {
{"indexes", indexes}, {"version", version}, {"size", size},
- {"recreate", recreate}, {"update", update}, {NULL, NULL}};
+ {"recreate", recreate}, {"update", update}, {"track_by_id", track_by_id},
+ {NULL, NULL}};
/*
* Struct to be used as userdata for the Lua representation of database records.
diff --git a/src/tangara/lua/lua_database.hpp b/src/tangara/lua/lua_database.hpp
index 328004ef..51e71758 100644
--- a/src/tangara/lua/lua_database.hpp
+++ b/src/tangara/lua/lua_database.hpp
@@ -14,6 +14,8 @@ namespace lua {
auto db_check_iterator(lua_State*, int stack_pos) -> database::Iterator*;
+auto pushTagValue(lua_State* L, const database::TagValue& val) -> void;
+
auto RegisterDatabaseModule(lua_State*) -> void;
} // namespace lua
diff --git a/src/tangara/lua/lua_queue.cpp b/src/tangara/lua/lua_queue.cpp
index 7eb32c62..07093390 100644
--- a/src/tangara/lua/lua_queue.cpp
+++ b/src/tangara/lua/lua_queue.cpp
@@ -79,10 +79,24 @@ static auto queue_open_playlist(lua_State* state) -> int {
return 0;
}
+static auto queue_play_from(lua_State* state) -> int {
+ Bridge* instance = Bridge::Get(state);
+ audio::TrackQueue& queue = instance->services().track_queue();
+ size_t len = 0;
+ const char* str = luaL_checklstring(state, 1, &len);
+ if (!str) {
+ return 0;
+ }
+ auto pos = luaL_checkinteger(state, 2);
+ queue.playFromPosition(str, pos);
+ return 0;
+}
+
static const struct luaL_Reg kQueueFuncs[] = {
{"add", queue_add},
{"clear", queue_clear},
{"open_playlist", queue_open_playlist},
+ {"play_from", queue_play_from},
{NULL, NULL}};
static auto lua_queue(lua_State* state) -> int {
diff --git a/src/tangara/lua/property.cpp b/src/tangara/lua/property.cpp
index 1be1fd2d..847bbe15 100644
--- a/src/tangara/lua/property.cpp
+++ b/src/tangara/lua/property.cpp
@@ -19,6 +19,7 @@
#include "lauxlib.h"
#include "lua.h"
#include "lua.hpp"
+#include "lua/lua_database.hpp"
#include "lua/lua_thread.hpp"
#include "lvgl.h"
#include "memory_resource.hpp"
@@ -240,29 +241,6 @@ auto Property::set(const LuaValue& val) -> bool {
return true;
}
-static auto pushTagValue(lua_State* L, const database::TagValue& val) -> void {
- std::visit(
- [&](auto&& arg) {
- using T = std::decay_t<decltype(arg)>;
- if constexpr (std::is_same_v<T, std::pmr::string>) {
- lua_pushlstring(L, arg.data(), arg.size());
- } else if constexpr (std::is_same_v<
- T, std::span<const std::pmr::string>>) {
- lua_createtable(L, 0, arg.size());
- for (const auto& i : arg) {
- lua_pushlstring(L, i.data(), i.size());
- lua_pushboolean(L, true);
- lua_rawset(L, -3);
- }
- } else if constexpr (std::is_same_v<T, uint32_t>) {
- lua_pushinteger(L, arg);
- } else {
- lua_pushnil(L);
- }
- },
- val);
-}
-
static void pushTrack(lua_State* L, const audio::TrackInfo& track) {
lua_newtable(L);