diff --git a/lua/file_browser.lua b/lua/file_browser.lua index 91b84c84..aed4aef8 100644 --- a/lua/file_browser.lua +++ b/lua/file_browser.lua @@ -62,10 +62,14 @@ return screen:new{ if is_dir then backstack.push(require("file_browser"):new{ title = self.title, - iterator = filesystem.iterator(tostring(item)), - breadcrumb = tostring(item) + iterator = filesystem.iterator(item:filepath()), + breadcrumb = item:filepath() }) end + if item:filepath():match("%.playlist$") then + queue.open_playlist(item:filepath()) + backstack.push(playing:new()) + end end end }) diff --git a/src/tangara/audio/playlist.cpp b/src/tangara/audio/playlist.cpp index 506e473f..944ad143 100644 --- a/src/tangara/audio/playlist.cpp +++ b/src/tangara/audio/playlist.cpp @@ -15,7 +15,7 @@ namespace audio { [[maybe_unused]] static constexpr char kTag[] = "playlist"; -Playlist::Playlist(std::string playlistFilepath) +Playlist::Playlist(const std::string& playlistFilepath) : filepath_(playlistFilepath), mutex_(), total_size_(0), @@ -49,7 +49,7 @@ auto Playlist::size() const -> size_t { return total_size_; } -auto Playlist::append(Item i) -> void { +auto MutablePlaylist::append(Item i) -> void { std::unique_lock lock(mutex_); auto offset = f_tell(&file_); bool first_entry = current_value_.empty(); @@ -126,7 +126,9 @@ auto Playlist::value() const -> std::string { return current_value_; } -auto Playlist::clear() -> bool { +MutablePlaylist::MutablePlaylist(const std::string& playlistFilepath) : Playlist(playlistFilepath) {} + +auto MutablePlaylist::clear() -> bool { std::unique_lock lock(mutex_); auto res = f_close(&file_); if (res != FR_OK) { diff --git a/src/tangara/audio/playlist.hpp b/src/tangara/audio/playlist.hpp index 3d914663..b248ac77 100644 --- a/src/tangara/audio/playlist.hpp +++ b/src/tangara/audio/playlist.hpp @@ -26,23 +26,21 @@ namespace audio { */ class Playlist { public: - Playlist(std::string playlistFilepath); - ~Playlist(); + Playlist(const std::string& playlistFilepath); + virtual ~Playlist(); using Item = std::variant; 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: + protected: std::string filepath_; std::mutex mutex_; size_t total_size_; @@ -62,4 +60,11 @@ class Playlist { auto advanceBy(ssize_t amt) -> bool; }; +class MutablePlaylist : public Playlist { +public: + MutablePlaylist(const std::string& playlistFilepath); + auto clear() -> bool; + auto append(Item i) -> void; +}; + } // 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 1aeecf8a..399d6717 100644 --- a/src/tangara/audio/track_queue.cpp +++ b/src/tangara/audio/track_queue.cpp @@ -84,18 +84,25 @@ auto notifyChanged(bool current_changed, Reason reason) -> void { events::Audio().Dispatch(ev); } + TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db) : mutex_(), bg_worker_(bg_worker), db_(db), - playlist_("queue.playlist"), // TODO + playlist_(".queue.playlist"), + position_(0), shuffle_(), repeat_(false), replay_(false) {} auto TrackQueue::current() const -> TrackItem { const std::shared_lock lock(mutex_); - std::string val = playlist_.value(); + std::string val; + if (opened_playlist_ && position_ < opened_playlist_->size()) { + val = opened_playlist_->value(); + } else { + val = playlist_.value(); + } if (val.empty()) { return {}; } @@ -104,12 +111,21 @@ auto TrackQueue::current() const -> TrackItem { auto TrackQueue::currentPosition() const -> size_t { const std::shared_lock lock(mutex_); - return playlist_.currentPosition(); + return position_; } auto TrackQueue::totalSize() const -> size_t { - const std::shared_lock lock(mutex_); - return playlist_.size(); + size_t sum = playlist_.size(); + if (opened_playlist_) { + sum += opened_playlist_->size(); + } + return sum; +} + +auto TrackQueue::updateShuffler() -> void { + if (shuffle_) { + shuffle_->resize(totalSize()); + } } auto TrackQueue::open() -> bool { @@ -118,6 +134,17 @@ auto TrackQueue::open() -> bool { return playlist_.open(); } +auto TrackQueue::openPlaylist(const std::string& playlist_file) -> bool { + opened_playlist_.emplace(playlist_file); + auto res = opened_playlist_->open(); + if (!res) { + return false; + } + updateShuffler(); + notifyChanged(true, Reason::kExplicitUpdate); + return true; +} + auto TrackQueue::getFilepath(database::TrackId id) -> std::optional { auto db = db_.lock(); if (!db) { @@ -142,20 +169,15 @@ auto TrackQueue::append(Item i) -> void { current_changed = was_queue_empty; // Dont support inserts yet } - auto update_shuffler = [=, this]() { - if (shuffle_) { - 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. - // We don't base this purely off of current_changed because we would like - // 'play this track now' (by inserting at the current pos) to work even - // when shuffling is enabled. - if (was_queue_empty) { - playlist_.skipTo(shuffle_->current()); - } - } - }; + // 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. + // We don't base this purely off of current_changed because we would like + // 'play this track now' (by inserting at the current pos) to work even + // when shuffling is enabled. + if (was_queue_empty && shuffle_) { + playlist_.skipTo(shuffle_->current()); + } if (std::holds_alternative(i)) { { @@ -164,7 +186,7 @@ auto TrackQueue::append(Item i) -> void { if (!filename.empty()) { playlist_.append(filename); } - update_shuffler(); + updateShuffler(); } notifyChanged(current_changed, Reason::kExplicitUpdate); } else if (std::holds_alternative(i)) { @@ -191,7 +213,7 @@ auto TrackQueue::append(Item i) -> void { } { const std::unique_lock lock(mutex_); - update_shuffler(); + updateShuffler(); } notifyChanged(current_changed, Reason::kExplicitUpdate); }); @@ -202,6 +224,20 @@ auto TrackQueue::next() -> void { next(Reason::kExplicitUpdate); } +auto TrackQueue::goTo(size_t position) { + position_ = position; + if (opened_playlist_) { + if (position_ < opened_playlist_->size()) { + opened_playlist_->skipTo(position_); + } else { + playlist_.skipTo(position_ - opened_playlist_->size()); + } + } else { + playlist_.skipTo(position_); + } +} + + auto TrackQueue::next(Reason r) -> void { bool changed = true; @@ -209,18 +245,13 @@ auto TrackQueue::next(Reason r) -> void { const std::unique_lock lock(mutex_); if (shuffle_) { shuffle_->next(); - playlist_.skipTo(shuffle_->current()); + position_ = shuffle_->current(); } else { - if (playlist_.atEnd()) { - if (replay_) { - playlist_.skipTo(0); - } else { - changed = false; - } - } else { - playlist_.next(); + if (position_ + 1 < totalSize()) { + position_++; } } + goTo(position_); } notifyChanged(changed, r); @@ -233,18 +264,13 @@ auto TrackQueue::previous() -> void { const std::unique_lock lock(mutex_); if (shuffle_) { shuffle_->prev(); - playlist_.skipTo(shuffle_->current()); + position_ = shuffle_->current(); } else { - if (playlist_.currentPosition() == 0) { - if (repeat_) { - playlist_.skipTo(playlist_.size()-1); - } else { - changed = false; - } - } else { - playlist_.prev(); + if (position_ > 0) { + position_--; } } + goTo(position_); } notifyChanged(changed, Reason::kExplicitUpdate); @@ -262,6 +288,7 @@ auto TrackQueue::clear() -> void { { const std::unique_lock lock(mutex_); playlist_.clear(); + opened_playlist_.reset(); if (shuffle_) { shuffle_->resize(0); } @@ -274,7 +301,7 @@ auto TrackQueue::random(bool en) -> void { { const std::unique_lock lock(mutex_); if (en) { - shuffle_.emplace(playlist_.size()); + shuffle_.emplace(totalSize()); shuffle_->replay(replay_); } else { shuffle_.reset(); @@ -326,7 +353,8 @@ auto TrackQueue::serialise() -> std::string { encoded.add(cppbor::Uint{0}, cppbor::Array{ cppbor::Bool{repeat_}, cppbor::Bool{replay_}, - cppbor::Uint{playlist_.currentPosition()}, + cppbor::Uint{position_}, + cppbor::Tstr{opened_playlist_->filepath()} }); if (shuffle_) { encoded.add(cppbor::Uint{1}, cppbor::Array{ @@ -368,7 +396,10 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item( i_ = 0; } else if (item->type() == cppbor::UINT) { auto val = item->asUint()->unsignedValue(); - queue_.playlist_.skipTo(val); + queue_.goTo(val); + } else if (item->type() == cppbor::TSTR) { + auto val = item->asTstr(); + queue_.openPlaylist(val->value()); } else if (item->type() == cppbor::SIMPLE) { bool val = item->asBool()->value(); if (i_ == 0) { diff --git a/src/tangara/audio/track_queue.hpp b/src/tangara/audio/track_queue.hpp index 6f50f162..72713242 100644 --- a/src/tangara/audio/track_queue.hpp +++ b/src/tangara/audio/track_queue.hpp @@ -74,11 +74,14 @@ class TrackQueue { auto currentPosition() const -> size_t; auto totalSize() const -> size_t; auto open() -> bool; + auto openPlaylist(const std::string& playlist_file) -> bool; using Item = std::variant; auto insert(Item, size_t index = 0) -> void; auto append(Item i) -> void; + auto updateShuffler() -> void; + /* * Advances to the next track in the queue, placing the current track at the * front of the 'played' queue. @@ -114,6 +117,7 @@ class TrackQueue { private: auto next(QueueUpdate::Reason r) -> void; + auto goTo(size_t position); auto getFilepath(database::TrackId id) -> std::optional; mutable std::shared_mutex mutex_; @@ -121,7 +125,10 @@ class TrackQueue { tasks::WorkerPool& bg_worker_; database::Handle db_; - Playlist playlist_; + MutablePlaylist playlist_; + std::optional opened_playlist_; + + size_t position_; std::optional shuffle_; bool repeat_; diff --git a/src/tangara/lua/file_iterator.cpp b/src/tangara/lua/file_iterator.cpp index c3d63a16..823775e8 100644 --- a/src/tangara/lua/file_iterator.cpp +++ b/src/tangara/lua/file_iterator.cpp @@ -15,8 +15,8 @@ namespace lua { [[maybe_unused]] static const char* kTag = "FileIterator"; -FileIterator::FileIterator(std::string filepath) - : original_path_(filepath), current_(), offset_(-1) { +FileIterator::FileIterator(std::string filepath, bool showHidden) + : original_path_(filepath), show_hidden_(showHidden), current_(), offset_(-1) { const TCHAR* path = static_cast(filepath.c_str()); FRESULT res = f_opendir(&dir_, path); if (res != FR_OK) { @@ -33,7 +33,16 @@ auto FileIterator::value() const -> const std::optional& { } auto FileIterator::next() -> void { - iterate(false); + size_t prev_index = -1; + if (current_) { + prev_index = current_->index; + } + do { + bool res = iterate(show_hidden_); + if (!res) { + break; + } + } while (!current_ || current_->index == prev_index); } auto FileIterator::prev() -> void { @@ -45,11 +54,11 @@ auto FileIterator::prev() -> void { auto new_offset = offset_ - 1; offset_ = -1; for (int i = 0; i <= new_offset; i++) { - iterate(false); + iterate(show_hidden_); } } -auto FileIterator::iterate(bool reverse) -> bool { +auto FileIterator::iterate(bool show_hidden) -> bool { FILINFO info; auto res = f_readdir(&dir_, &info); if (res != FR_OK) { @@ -60,18 +69,22 @@ auto FileIterator::iterate(bool reverse) -> bool { // End of directory // Set value to nil current_.reset(); + return false; } else { // Update current value offset_++; - current_ = FileEntry{ - .index = offset_, - .isHidden = (info.fattrib & AM_HID) > 0, - .isDirectory = (info.fattrib & AM_DIR) > 0, - .isTrack = false, // TODO - .filepath = original_path_ + (original_path_.size() > 0 ? "/" : "") + - info.fname, - - }; + bool hidden = (info.fattrib & AM_HID) > 0 || info.fname[0] == '.'; + if (!hidden || show_hidden) { + current_ = FileEntry{ + .index = offset_, + .isHidden = hidden, + .isDirectory = (info.fattrib & AM_DIR) > 0, + .isTrack = false, // TODO + .filepath = original_path_ + (original_path_.size() > 0 ? "/" : "") + + info.fname, + .name = info.fname, + }; + } } return true; } diff --git a/src/tangara/lua/file_iterator.hpp b/src/tangara/lua/file_iterator.hpp index b803062c..c4071445 100644 --- a/src/tangara/lua/file_iterator.hpp +++ b/src/tangara/lua/file_iterator.hpp @@ -21,11 +21,12 @@ struct FileEntry { bool isDirectory; bool isTrack; std::string filepath; + std::string name; }; class FileIterator { public: - FileIterator(std::string filepath); + FileIterator(std::string filepath, bool showHidden); ~FileIterator(); auto value() const -> const std::optional&; @@ -35,6 +36,7 @@ class FileIterator { private: FF_DIR dir_; std::string original_path_; + bool show_hidden_; std::optional current_; int offset_; diff --git a/src/tangara/lua/lua_filesystem.cpp b/src/tangara/lua/lua_filesystem.cpp index de51f555..e3a3018d 100644 --- a/src/tangara/lua/lua_filesystem.cpp +++ b/src/tangara/lua/lua_filesystem.cpp @@ -21,29 +21,21 @@ struct LuaFileEntry { bool isHidden; bool isDirectory; bool isTrack; - size_t path_size; - char path[]; + std::string path; + std::string name; }; -static_assert(std::is_trivially_destructible()); -static_assert(std::is_trivially_copy_assignable()); - static auto push_lua_file_entry(lua_State* L, const lua::FileEntry& r) -> void { - // Create and init the userdata. - LuaFileEntry* file_entry = reinterpret_cast( - lua_newuserdata(L, sizeof(LuaFileEntry) + r.filepath.size())); + lua::FileEntry** entry = reinterpret_cast( + lua_newuserdata(L, sizeof(uintptr_t))); + *entry = new lua::FileEntry(r); luaL_setmetatable(L, kFileEntryMetatable); +} - // Init all the fields - *file_entry = { - .isHidden = r.isHidden, - .isDirectory = r.isDirectory, - .isTrack = r.isTrack, - .path_size = r.filepath.size(), - }; - - // Copy the string data across. - std::memcpy(file_entry->path, r.filepath.data(), r.filepath.size()); +auto check_file_entry(lua_State* L, int stack_pos) -> lua::FileEntry* { + lua::FileEntry* entry = *reinterpret_cast( + luaL_checkudata(L, stack_pos, kFileEntryMetatable)); + return entry; } auto check_file_iterator(lua_State* L, int stack_pos) -> lua::FileIterator* { @@ -56,7 +48,7 @@ static auto push_iterator(lua_State* state, const lua::FileIterator& it) -> void { lua::FileIterator** data = reinterpret_cast( lua_newuserdata(state, sizeof(uintptr_t))); - *data = new lua::FileIterator(it); // TODO... + *data = new lua::FileIterator(it); luaL_setmetatable(state, kFileIteratorMetatable); } @@ -108,45 +100,55 @@ static const struct luaL_Reg kFileIteratorFuncs[] = {{"next", fs_iterate}, {NULL, NULL}}; static auto file_entry_path(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushlstring(state, data->path, data->path_size); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushlstring(state, entry->filepath.c_str(), entry->filepath.size()); return 1; } static auto file_entry_is_dir(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushboolean(state, data->isDirectory); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushboolean(state, entry->isDirectory); return 1; } static auto file_entry_is_hidden(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushboolean(state, data->isHidden); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushboolean(state, entry->isHidden); return 1; } static auto file_entry_is_track(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushboolean(state, data->isTrack); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushboolean(state, entry->isTrack); + return 1; +} + +static auto file_entry_name(lua_State* state) -> int { + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushlstring(state, entry->name.c_str(), entry->name.size()); + return 1; +} + +static auto file_entry_gc(lua_State* state) -> int { + lua::FileEntry* entry = check_file_entry(state, 1); + delete entry; return 1; } static const struct luaL_Reg kFileEntryFuncs[] = {{"filepath", file_entry_path}, + {"name", file_entry_name}, {"is_directory", file_entry_is_dir}, {"is_hidden", file_entry_is_hidden}, {"is_track", file_entry_is_track}, - {"__tostring", file_entry_path}, + {"__tostring", file_entry_name}, + {"__gc", file_entry_gc}, {NULL, NULL}}; static auto fs_new_iterator(lua_State* state) -> int { // Takes a filepath as a string and returns a new FileIterator // on that directory std::string filepath = luaL_checkstring(state, -1); - lua::FileIterator iter(filepath); + lua::FileIterator iter(filepath, false); push_iterator(state, iter); return 1; } diff --git a/src/tangara/lua/lua_queue.cpp b/src/tangara/lua/lua_queue.cpp index bc393aa5..9e2002e6 100644 --- a/src/tangara/lua/lua_queue.cpp +++ b/src/tangara/lua/lua_queue.cpp @@ -57,8 +57,22 @@ static auto queue_clear(lua_State* state) -> int { return 0; } +static auto queue_open_playlist(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; + } + queue.clear(); + queue.openPlaylist(str); + return 0; +} + static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, {"clear", queue_clear}, + {"open_playlist", queue_open_playlist}, {NULL, NULL}}; static auto lua_queue(lua_State* state) -> int {