Merge branch 'main' of codeberg.org:cool-tech-zone/tangara-fw

custom
jacqueline 7 months ago
commit 9c95c2b422
  1. 8
      lua/browser.lua
  2. 14
      luals-stubs/database.lua
  3. 6
      luals-stubs/queue.lua
  4. 2
      src/tangara/audio/audio_events.hpp
  5. 35
      src/tangara/audio/audio_fsm.cpp
  6. 3
      src/tangara/audio/audio_fsm.hpp
  7. 23
      src/tangara/audio/track_queue.cpp
  8. 1
      src/tangara/audio/track_queue.hpp
  9. 19
      src/tangara/database/database.cpp
  10. 5
      src/tangara/database/database.hpp
  11. 9
      src/tangara/database/records.cpp
  12. 12
      src/tangara/database/track.cpp
  13. 7
      src/tangara/database/track.hpp
  14. 72
      src/tangara/lua/lua_database.cpp
  15. 2
      src/tangara/lua/lua_database.hpp
  16. 14
      src/tangara/lua/lua_queue.cpp
  17. 24
      src/tangara/lua/property.cpp

@ -8,6 +8,7 @@ local styles = require("styles")
local playback = require("playback")
local theme = require("theme")
local screen = require("screen")
local database = require("database")
return screen:new{
create_ui = function(self)
@ -101,7 +102,12 @@ return screen:new{
})
else
queue.clear()
queue.add(contents)
local track = database.track_by_id(contents)
if (track) then
queue.play_from(track.filepath, track.saved_position)
else
queue.add(contents)
end
playback.playing:set(true)
backstack.push(playing:new())
end

@ -10,6 +10,19 @@ local database = {}
--- @return Index[]
function database.indexes() end
--- Returns the track in the database with the id given
--- @param id TrackId
--- @return Track
function database.track_by_id(id) end
--- @class Track
--- @field id TrackId The track id of this track
--- @field filepath string The filepath of this track
--- @field saved_position integer The last saved position of this track
--- @field tags table A mapping of any available tags to that tag's value
local Track = {}
--- An iterator is a userdata type that behaves like an ordinary Lua iterator.
--- @class Iterator
local Iterator = {}
@ -19,6 +32,7 @@ local Iterator = {}
--- @class TrackId
local TrackId = {}
--- Gets the human-readable text representing this record. The `__tostring`
--- metatable function is an alias of this function.
--- @class Record

@ -33,4 +33,10 @@ function queue.next() end
--- Moves backward in the play queue, looping back around to the end if repeat is on.
function queue.previous() end
--- Play a track starting from a number of seconds in
--- This will replace the existing queue
--- @param filepath string
--- @param seconds_offset integer
function queue.play_from(filepath, seconds_offset) end
return queue

@ -106,6 +106,8 @@ struct QueueUpdate : tinyfsm::Event {
kBulkLoadingUpdate,
};
Reason reason;
std::optional<uint32_t> seek_to_second;
};
struct StepUpVolume : tinyfsm::Event {};

@ -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);

@ -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;
};

@ -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;

@ -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>;

@ -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.

@ -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;

@ -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;
}

@ -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

@ -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;
};

@ -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.

@ -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

@ -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 {

@ -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);

Loading…
Cancel
Save