summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-11-23 14:08:46 +1100
committerjacqueline <me@jacqueline.id.au>2023-11-23 14:08:46 +1100
commit09c0e1608f2d88f56d8bf87ff90482459376ad95 (patch)
treee675756aff8c8f78fe89c4f2f9e30c713f10cbfe
parentb07bfbc6c70fd0bba8dff85fe4149feb9fa9b8d4 (diff)
downloadtangara-fw-09c0e1608f2d88f56d8bf87ff90482459376ad95.tar.gz
Implement adding to the playback queue from lua
-rw-r--r--lua/browser.lua21
-rw-r--r--lua/img/bat/chg.pngbin666 -> 667 bytes
-rw-r--r--lua/img/play_small.pngbin593 -> 593 bytes
-rw-r--r--lua/main_menu.lua13
-rw-r--r--lua/widgets.lua1
-rw-r--r--src/database/database.cpp31
-rw-r--r--src/database/include/database.hpp6
-rw-r--r--src/lua/include/lua_database.hpp4
-rw-r--r--src/lua/lua_database.cpp72
-rw-r--r--src/lua/lua_queue.cpp21
-rw-r--r--src/playlist/include/source.hpp19
-rw-r--r--src/playlist/source.cpp49
12 files changed, 196 insertions, 41 deletions
diff --git a/lua/browser.lua b/lua/browser.lua
index ccb2dae8..f07d80bc 100644
--- a/lua/browser.lua
+++ b/lua/browser.lua
@@ -4,7 +4,7 @@ local legacy_ui = require("legacy_ui")
local database = require("database")
local backstack = require("backstack")
local font = require("font")
-local playing = require("playing")
+local queue = require("queue")
local browser = {}
@@ -40,7 +40,6 @@ function browser.create(opts)
h = lvgl.SIZE_CONTENT,
pad_left = 4,
pad_right = 4,
- pad_top = 2,
pad_bottom = 2,
bg_opa = lvgl.OPA(100),
bg_color = "#fafafa",
@@ -64,9 +63,18 @@ function browser.create(opts)
h = lvgl.SIZE_CONTENT,
pad_column = 4,
})
+ local original_iterator = opts.iterator:clone()
local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue")
+ enqueue:onClicked(function()
+ queue.add(original_iterator)
+ end)
-- enqueue:add_flag(lvgl.FLAG.HIDDEN)
local play = widgets.IconBtn(buttons, "//lua/img/play_small.png", "Play")
+ play:onClicked(function()
+ queue.clear()
+ queue.add(original_iterator)
+ end
+ )
end
screen.list = lvgl.List(screen.root, {
@@ -88,7 +96,7 @@ function browser.create(opts)
local btn = screen.list:add_btn(nil, tostring(item))
btn:onClicked(function()
local contents = item:contents()
- if type(contents) == "function" then
+ if type(contents) == "userdata" then
backstack.push(function()
return browser.create({
title = opts.title,
@@ -97,7 +105,8 @@ function browser.create(opts)
})
end)
else
- print("add", contents)
+ queue.clear()
+ queue.add(contents)
legacy_ui.open_now_playing()
-- backstack.push(playing)
end
@@ -105,13 +114,13 @@ function browser.create(opts)
btn:onevent(lvgl.EVENT.FOCUSED, function()
screen.focused_item = this_item
if screen.last_item - 5 < this_item then
- opts.iterator(screen.add_item)
+ opts.iterator:next(screen.add_item)
end
end)
end
for _ = 1, 8 do
- opts.iterator(screen.add_item)
+ opts.iterator:next(screen.add_item)
end
return screen
diff --git a/lua/img/bat/chg.png b/lua/img/bat/chg.png
index af90f02b..d604bb76 100644
--- a/lua/img/bat/chg.png
+++ b/lua/img/bat/chg.png
Binary files differ
diff --git a/lua/img/play_small.png b/lua/img/play_small.png
index a7361be4..3fc7032e 100644
--- a/lua/img/play_small.png
+++ b/lua/img/play_small.png
Binary files differ
diff --git a/lua/main_menu.lua b/lua/main_menu.lua
index 50e930fb..c0b9b1d1 100644
--- a/lua/main_menu.lua
+++ b/lua/main_menu.lua
@@ -36,13 +36,12 @@ return function()
for id, idx in ipairs(indexes) do
local btn = menu.list:add_btn(nil, tostring(idx))
btn:onClicked(function()
- legacy_ui.open_browse(id);
- -- backstack.push(function()
- -- return browser {
- -- title = tostring(idx),
- -- iterator = idx:iter()
- -- }
- -- end)
+ backstack.push(function()
+ return browser {
+ title = tostring(idx),
+ iterator = idx:iter()
+ }
+ end)
end)
end
diff --git a/lua/widgets.lua b/lua/widgets.lua
index 95615a8c..b601326b 100644
--- a/lua/widgets.lua
+++ b/lua/widgets.lua
@@ -151,6 +151,7 @@ function widgets.IconBtn(parent, icon, text)
}
btn:Image { src = icon }
btn:Label { text = text, text_font = font.fusion_10 }
+ return btn
end
return widgets
diff --git a/src/database/database.cpp b/src/database/database.cpp
index dad983d0..76d3a2ab 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -874,6 +874,12 @@ Iterator::Iterator(std::weak_ptr<Database> db, const IndexInfo& idx)
Iterator::Iterator(std::weak_ptr<Database> db, const Continuation& c)
: db_(db), pos_mutex_(), current_pos_(c), prev_pos_() {}
+Iterator::Iterator(const Iterator& other)
+ : db_(other.db_),
+ pos_mutex_(),
+ current_pos_(other.current_pos_),
+ prev_pos_(other.prev_pos_) {}
+
auto Iterator::Next(Callback cb) -> void {
auto db = db_.lock();
if (!db) {
@@ -899,6 +905,31 @@ auto Iterator::Next(Callback cb) -> void {
});
}
+auto Iterator::NextSync() -> std::optional<IndexRecord> {
+ auto db = db_.lock();
+ if (!db) {
+ return {};
+ }
+ return db->worker_task_
+ ->Dispatch<std::optional<IndexRecord>>(
+ [=]() -> std::optional<IndexRecord> {
+ std::lock_guard lock{pos_mutex_};
+ if (!current_pos_) {
+ return {};
+ }
+ std::unique_ptr<Result<IndexRecord>> res{
+ db->dbGetPage<IndexRecord>(*current_pos_)};
+ prev_pos_ = current_pos_;
+ current_pos_ = res->next_page();
+ if (!res || res->values().empty() || !res->values()[0]) {
+ ESP_LOGI(kTag, "dropping empty result");
+ return {};
+ }
+ return *res->values()[0];
+ })
+ .get();
+}
+
auto Iterator::Prev(Callback cb) -> void {
auto db = db_.lock();
if (!db) {
diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp
index e18701eb..36f734b8 100644
--- a/src/database/include/database.hpp
+++ b/src/database/include/database.hpp
@@ -193,12 +193,18 @@ class Iterator {
public:
Iterator(std::weak_ptr<Database>, const IndexInfo&);
Iterator(std::weak_ptr<Database>, const Continuation&);
+ Iterator(const Iterator &);
+
+ auto database() const { return db_; }
using Callback = std::function<void(std::optional<IndexRecord>)>;
auto Next(Callback) -> void;
+ auto NextSync() -> std::optional<IndexRecord>;
+
auto Prev(Callback) -> void;
+
private:
auto InvokeNull(Callback) -> void;
diff --git a/src/lua/include/lua_database.hpp b/src/lua/include/lua_database.hpp
index e47ace08..b0d2acbd 100644
--- a/src/lua/include/lua_database.hpp
+++ b/src/lua/include/lua_database.hpp
@@ -8,8 +8,12 @@
#include "lua.hpp"
+#include "database.hpp"
+
namespace lua {
+auto db_check_iterator(lua_State*, int stack_pos) -> database::Iterator*;
+
auto RegisterDatabaseModule(lua_State*) -> void;
} // namespace lua
diff --git a/src/lua/lua_database.cpp b/src/lua/lua_database.cpp
index 4a7c82a5..d41f4794 100644
--- a/src/lua/lua_database.cpp
+++ b/src/lua/lua_database.cpp
@@ -32,7 +32,7 @@ namespace lua {
static constexpr char kDbIndexMetatable[] = "db_index";
static constexpr char kDbRecordMetatable[] = "db_record";
-static constexpr char kDbIteratorMetatable[] = "db_record";
+static constexpr char kDbIteratorMetatable[] = "db_iterator";
static auto indexes(lua_State* state) -> int {
Bridge* instance = Bridge::Get(state);
@@ -94,13 +94,37 @@ static auto push_lua_record(lua_State* L, const database::IndexRecord& r)
std::memcpy(record->text, text.data(), text.size());
}
+auto db_check_iterator(lua_State* L, int stack_pos) -> database::Iterator* {
+ database::Iterator* it = *reinterpret_cast<database::Iterator**>(
+ luaL_checkudata(L, stack_pos, kDbIteratorMetatable));
+ return it;
+}
+
+static auto push_iterator(lua_State* state,
+ std::variant<database::Iterator*,
+ database::Continuation,
+ database::IndexInfo> val) -> void {
+ Bridge* instance = Bridge::Get(state);
+ database::Iterator** data = reinterpret_cast<database::Iterator**>(
+ lua_newuserdata(state, sizeof(uintptr_t)));
+ std::visit(
+ [&](auto&& arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, database::Iterator*>) {
+ *data = new database::Iterator(*arg);
+ } else {
+ *data = new database::Iterator(instance->services().database(), arg);
+ }
+ },
+ val);
+ luaL_setmetatable(state, kDbIteratorMetatable);
+}
+
static auto db_iterate(lua_State* state) -> int {
- luaL_checktype(state, 1, LUA_TFUNCTION);
+ database::Iterator* it = db_check_iterator(state, 1);
+ luaL_checktype(state, 2, LUA_TFUNCTION);
int callback_ref = luaL_ref(state, LUA_REGISTRYINDEX);
- database::Iterator* it = *reinterpret_cast<database::Iterator**>(
- lua_touserdata(state, lua_upvalueindex(1)));
-
it->Next([=](std::optional<database::IndexRecord> res) {
events::Ui().RunOnTask([=]() {
lua_rawgeti(state, LUA_REGISTRYINDEX, callback_ref);
@@ -116,29 +140,22 @@ static auto db_iterate(lua_State* state) -> int {
return 0;
}
+static auto db_iterator_clone(lua_State* state) -> int {
+ database::Iterator* it = db_check_iterator(state, 1);
+ push_iterator(state, it);
+ return 1;
+}
+
static auto db_iterator_gc(lua_State* state) -> int {
- database::Iterator** it = reinterpret_cast<database::Iterator**>(
- luaL_checkudata(state, 1, kDbIteratorMetatable));
- if (it != NULL) {
- delete *it;
- }
+ database::Iterator* it = db_check_iterator(state, 1);
+ delete it;
return 0;
}
-static auto push_iterator(
- lua_State* state,
- std::variant<database::Continuation, database::IndexInfo> val) -> void {
- Bridge* instance = Bridge::Get(state);
- database::Iterator** data = reinterpret_cast<database::Iterator**>(
- lua_newuserdata(state, sizeof(uintptr_t)));
- std::visit(
- [&](auto&& arg) {
- *data = new database::Iterator(instance->services().database(), arg);
- },
- val);
- luaL_setmetatable(state, kDbIteratorMetatable);
- lua_pushcclosure(state, db_iterate, 1);
-}
+static const struct luaL_Reg kDbIteratorFuncs[] = {{"next", db_iterate},
+ {"clone", db_iterator_clone},
+ {"__gc", db_iterator_gc},
+ {NULL, NULL}};
static auto record_text(lua_State* state) -> int {
LuaRecord* data = reinterpret_cast<LuaRecord*>(
@@ -219,9 +236,10 @@ static auto lua_database(lua_State* state) -> int {
luaL_setfuncs(state, kDbIndexFuncs, 0);
luaL_newmetatable(state, kDbIteratorMetatable);
- lua_pushliteral(state, "__gc");
- lua_pushcfunction(state, db_iterator_gc);
- lua_settable(state, -3);
+ lua_pushliteral(state, "__index");
+ lua_pushvalue(state, -2);
+ lua_settable(state, -3); // metatable.__index = metatable
+ luaL_setfuncs(state, kDbIteratorFuncs, 0);
luaL_newmetatable(state, kDbRecordMetatable);
lua_pushliteral(state, "__index");
diff --git a/src/lua/lua_queue.cpp b/src/lua/lua_queue.cpp
index 500940a2..929a7159 100644
--- a/src/lua/lua_queue.cpp
+++ b/src/lua/lua_queue.cpp
@@ -21,6 +21,7 @@
#include "index.hpp"
#include "property.hpp"
#include "service_locator.hpp"
+#include "source.hpp"
#include "ui_events.hpp"
namespace lua {
@@ -28,10 +29,28 @@ namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_queue";
static auto queue_add(lua_State* state) -> int {
+ Bridge* instance = Bridge::Get(state);
+
+ if (lua_isinteger(state, 1)) {
+ instance->services().track_queue().AddLast(luaL_checkinteger(state, 1));
+ } else {
+ database::Iterator* it = db_check_iterator(state, 1);
+ instance->services().track_queue().IncludeLast(
+ std::make_shared<playlist::IteratorSource>(*it));
+ }
+
+ return 0;
+}
+
+static auto queue_clear(lua_State* state) -> int {
+ Bridge* instance = Bridge::Get(state);
+ instance->services().track_queue().Clear();
return 0;
}
-static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, {NULL, NULL}};
+static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add},
+ {"clear", queue_clear},
+ {NULL, NULL}};
static auto lua_queue(lua_State* state) -> int {
luaL_newlib(state, kQueueFuncs);
diff --git a/src/playlist/include/source.hpp b/src/playlist/include/source.hpp
index aa15e7df..ce12faf3 100644
--- a/src/playlist/include/source.hpp
+++ b/src/playlist/include/source.hpp
@@ -9,6 +9,7 @@
#include <deque>
#include <memory>
#include <mutex>
+#include <stack>
#include <variant>
#include <vector>
@@ -73,6 +74,24 @@ class IResetableSource : public ISource {
virtual auto Reset() -> void = 0;
};
+class IteratorSource : public IResetableSource {
+ public:
+ IteratorSource(const database::Iterator&);
+
+ auto Current() -> std::optional<database::TrackId> override;
+ auto Advance() -> std::optional<database::TrackId> override;
+ auto Peek(std::size_t n, std::vector<database::TrackId>*)
+ -> std::size_t override;
+
+ auto Previous() -> std::optional<database::TrackId> override;
+ auto Reset() -> void override;
+
+ private:
+ const database::Iterator& start_;
+ std::optional<database::TrackId> current_;
+ std::stack<database::Iterator, std::vector<database::Iterator>> next_;
+};
+
auto CreateSourceFromResults(
std::weak_ptr<database::Database>,
std::shared_ptr<database::Result<database::IndexRecord>>)
diff --git a/src/playlist/source.cpp b/src/playlist/source.cpp
index 7a062bc7..2540c3fb 100644
--- a/src/playlist/source.cpp
+++ b/src/playlist/source.cpp
@@ -22,6 +22,55 @@
namespace playlist {
+[[maybe_unused]] static constexpr char kTag[] = "queue_src";
+
+IteratorSource::IteratorSource(const database::Iterator& it)
+ : start_(it), current_(), next_() {
+ Reset();
+ Advance();
+}
+
+auto IteratorSource::Current() -> std::optional<database::TrackId> {
+ return current_;
+}
+
+auto IteratorSource::Advance() -> std::optional<database::TrackId> {
+ ESP_LOGI(kTag, "advancing");
+ while (!next_.empty()) {
+ auto next = next_.top().NextSync();
+ if (!next) {
+ ESP_LOGI(kTag, "top was empty");
+ next_.pop();
+ continue;
+ }
+ if (next->track()) {
+ ESP_LOGI(kTag, "top held track %lu", next->track().value_or(0));
+ current_ = next->track();
+ return current_;
+ }
+ ESP_LOGI(kTag, "top held records");
+ next_.push(database::Iterator(start_.database(), next->Expand(1).value()));
+ }
+ ESP_LOGI(kTag, "exhausted");
+ return {};
+}
+
+auto IteratorSource::Peek(std::size_t n, std::vector<database::TrackId>*)
+ -> std::size_t {
+ return 0;
+}
+
+auto IteratorSource::Previous() -> std::optional<database::TrackId> {
+ return {};
+}
+
+auto IteratorSource::Reset() -> void {
+ while (!next_.empty()) {
+ next_.pop();
+ }
+ next_.push(start_);
+}
+
auto CreateSourceFromResults(
std::weak_ptr<database::Database> db,
std::shared_ptr<database::Result<database::IndexRecord>> results)