Queue repeat modes (#126)
This replaces the previous system of a separate track and queue repeat, with a RepeatMode type with the following options and behaviours: - OFF: No repeat, queue or track. When the current queue finishes, shuffled or otherwise, playback will stop. - REPEAT_TRACK: The current track will loop indefinitely, unless next is explicitly called through some user action (ie using the next button in the now playing screen) - REPEAT_QUEUE: The entire queue will repeat indefinitely. When shuffled is enabled this will repeat the queue with new combinations each cycle. The repeat mode is persisted in non-volatile storage, so the behaviour will be consistent throughout restarts and queue replacements, and so the "queue repeat by default" use case can be met in this way. In addition, I've made it work a little nicer when the queue runs out in the now playing screen, keeping the previously played track shown and playback can be continued by using the play button or by going to a previous song in the queue. Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/126 Co-authored-by: ailurux <ailuruxx@gmail.com> Co-committed-by: ailurux <ailuruxx@gmail.com>
This commit is contained in:
+2
-1
@@ -13,8 +13,9 @@ local img = {
|
|||||||
prev = lvgl.ImgData("//lua/img/prev.png"),
|
prev = lvgl.ImgData("//lua/img/prev.png"),
|
||||||
shuffle = lvgl.ImgData("//lua/img/shuffle.png"),
|
shuffle = lvgl.ImgData("//lua/img/shuffle.png"),
|
||||||
shuffle_off = lvgl.ImgData("//lua/img/shuffle_off.png"),
|
shuffle_off = lvgl.ImgData("//lua/img/shuffle_off.png"),
|
||||||
repeat_src = lvgl.ImgData("//lua/img/repeat.png"), -- repeat is a reserved word
|
repeat_track = lvgl.ImgData("//lua/img/repeat.png"),
|
||||||
repeat_off = lvgl.ImgData("//lua/img/repeat_off.png"),
|
repeat_off = lvgl.ImgData("//lua/img/repeat_off.png"),
|
||||||
|
repeat_queue = lvgl.ImgData("//lua/img/repeat_queue.png"),
|
||||||
queue = lvgl.ImgData("//lua/img/queue.png"),
|
queue = lvgl.ImgData("//lua/img/queue.png"),
|
||||||
files = lvgl.ImgData("//lua/img/files.png"),
|
files = lvgl.ImgData("//lua/img/files.png"),
|
||||||
settings = lvgl.ImgData("//lua/img/settings.png"),
|
settings = lvgl.ImgData("//lua/img/settings.png"),
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
+20
-17
@@ -176,9 +176,9 @@ return screen:new {
|
|||||||
|
|
||||||
local repeat_btn = controls:Button {}
|
local repeat_btn = controls:Button {}
|
||||||
repeat_btn:onClicked(function()
|
repeat_btn:onClicked(function()
|
||||||
queue.repeat_track:set(not queue.repeat_track:get())
|
queue.repeat_mode:set((queue.repeat_mode:get() + 1) % 3)
|
||||||
end)
|
end)
|
||||||
local repeat_img = repeat_btn:Image { src = img.repeat_src }
|
local repeat_img = repeat_btn:Image { src = img.repeat_off }
|
||||||
theme.set_subject(repeat_btn, icon_enabled_class)
|
theme.set_subject(repeat_btn, icon_enabled_class)
|
||||||
local repeat_desc = widgets.Description(repeat_btn)
|
local repeat_desc = widgets.Description(repeat_btn)
|
||||||
|
|
||||||
@@ -197,6 +197,10 @@ return screen:new {
|
|||||||
|
|
||||||
local play_pause_btn = controls:Button {}
|
local play_pause_btn = controls:Button {}
|
||||||
play_pause_btn:onClicked(function()
|
play_pause_btn:onClicked(function()
|
||||||
|
if (not playback.track:get()) then
|
||||||
|
-- Restart the last played track
|
||||||
|
queue.position:set(queue.position:get())
|
||||||
|
end
|
||||||
playback.playing:set(not playback.playing:get())
|
playback.playing:set(not playback.playing:get())
|
||||||
end)
|
end)
|
||||||
play_pause_btn:focus()
|
play_pause_btn:focus()
|
||||||
@@ -238,7 +242,10 @@ return screen:new {
|
|||||||
text = format_time(pos)
|
text = format_time(pos)
|
||||||
}
|
}
|
||||||
local track = playback.track:get()
|
local track = playback.track:get()
|
||||||
if not track then return end
|
if not track then
|
||||||
|
scrubber:set{value = 0}
|
||||||
|
return
|
||||||
|
end
|
||||||
if not track.duration then return end
|
if not track.duration then return end
|
||||||
scrubber:set { value = pos / track.duration * 100 }
|
scrubber:set { value = pos / track.duration * 100 }
|
||||||
end
|
end
|
||||||
@@ -247,13 +254,7 @@ return screen:new {
|
|||||||
if not track then
|
if not track then
|
||||||
if queue.loading:get() then
|
if queue.loading:get() then
|
||||||
title:set { text = "Loading..." }
|
title:set { text = "Loading..." }
|
||||||
else
|
|
||||||
title:set { text = "" }
|
|
||||||
end
|
end
|
||||||
artist:set { text = "" }
|
|
||||||
cur_time:set { text = format_time(0) }
|
|
||||||
end_time:set { text = format_time(0) }
|
|
||||||
scrubber:set { value = 0 }
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if track.duration then
|
if track.duration then
|
||||||
@@ -268,7 +269,7 @@ return screen:new {
|
|||||||
if not pos then return end
|
if not pos then return end
|
||||||
playlist_pos:set { text = tostring(pos) }
|
playlist_pos:set { text = tostring(pos) }
|
||||||
|
|
||||||
local can_next = pos < queue.size:get() or queue.random:get()
|
local can_next = pos < queue.size:get() or queue.random:get() or queue.repeat_mode:get() == queue.RepeatMode.REPEAT_QUEUE
|
||||||
theme.set_subject(
|
theme.set_subject(
|
||||||
next_btn, can_next and icon_enabled_class or icon_disabled_class
|
next_btn, can_next and icon_enabled_class or icon_disabled_class
|
||||||
)
|
)
|
||||||
@@ -283,14 +284,16 @@ return screen:new {
|
|||||||
shuffle_desc:set { text = "Enable shuffle" }
|
shuffle_desc:set { text = "Enable shuffle" }
|
||||||
end
|
end
|
||||||
end),
|
end),
|
||||||
queue.repeat_track:bind(function(en)
|
queue.repeat_mode:bind(function(mode)
|
||||||
theme.set_subject(repeat_btn, en and icon_enabled_class or icon_disabled_class)
|
if mode == queue.RepeatMode.OFF then
|
||||||
if en then
|
|
||||||
repeat_img:set_src(img.repeat_src)
|
|
||||||
repeat_desc:set { text = "Disable track repeat" }
|
|
||||||
else
|
|
||||||
repeat_img:set_src(img.repeat_off)
|
repeat_img:set_src(img.repeat_off)
|
||||||
repeat_desc:set { text = "Enable track repeat" }
|
repeat_desc:set { text = "Repeat off" }
|
||||||
|
elseif mode == queue.RepeatMode.REPEAT_TRACK then
|
||||||
|
repeat_img:set_src(img.repeat_track)
|
||||||
|
repeat_desc:set { text = "Repeat track" }
|
||||||
|
elseif mode == queue.RepeatMode.REPEAT_QUEUE then
|
||||||
|
repeat_img:set_src(img.repeat_queue)
|
||||||
|
repeat_desc:set { text = "Repeat queue" }
|
||||||
end
|
end
|
||||||
end),
|
end),
|
||||||
queue.size:bind(function(num)
|
queue.size:bind(function(num)
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
--- @class queue
|
--- @class queue
|
||||||
--- @field position Property The index in the queue of the currently playing track. This may be zero if the queue is empty. Writeable.
|
--- @field position Property The index in the queue of the currently playing track. This may be zero if the queue is empty. Writeable.
|
||||||
--- @field size Property The total number of tracks in the queue, including tracks which have already been played.
|
--- @field size Property The total number of tracks in the queue, including tracks which have already been played.
|
||||||
--- @field replay Property Whether or not the queue will be restarted after the final track is played. Writeable.
|
--- @field repeat_mode Property The current repeat mode for the queue. Writeable.
|
||||||
--- @field repeat_track Property Whether or not the current track will repeat indefinitely. Writeable.
|
|
||||||
--- @field random Property Determines whether, when progressing to the next track in the queue, the next track will be chosen randomly. The random selection algorithm used is a Miller Shuffle, which guarantees that no repeat selections will be made until every item in the queue has been played. Writeable.
|
--- @field random Property Determines whether, when progressing to the next track in the queue, the next track will be chosen randomly. The random selection algorithm used is a Miller Shuffle, which guarantees that no repeat selections will be made until every item in the queue has been played. Writeable.
|
||||||
local queue = {}
|
local queue = {}
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ class NvsStorage {
|
|||||||
auto PrimaryInput() -> InputModes;
|
auto PrimaryInput() -> InputModes;
|
||||||
auto PrimaryInput(InputModes) -> void;
|
auto PrimaryInput(InputModes) -> void;
|
||||||
|
|
||||||
|
auto QueueRepeatMode() -> uint8_t;
|
||||||
|
auto QueueRepeatMode(uint8_t) -> void;
|
||||||
|
|
||||||
auto DbAutoIndex() -> bool;
|
auto DbAutoIndex() -> bool;
|
||||||
auto DbAutoIndex(bool) -> void;
|
auto DbAutoIndex(bool) -> void;
|
||||||
|
|
||||||
@@ -173,6 +176,8 @@ class NvsStorage {
|
|||||||
|
|
||||||
Setting<uint8_t> db_auto_index_;
|
Setting<uint8_t> db_auto_index_;
|
||||||
|
|
||||||
|
Setting<uint8_t> queue_repeat_mode_;
|
||||||
|
|
||||||
util::LruCache<10, bluetooth::mac_addr_t, uint8_t> bt_volumes_;
|
util::LruCache<10, bluetooth::mac_addr_t, uint8_t> bt_volumes_;
|
||||||
bool bt_volumes_dirty_;
|
bool bt_volumes_dirty_;
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ static constexpr char kKeyDisplayRows[] = "disprows";
|
|||||||
static constexpr char kKeyHapticMotorType[] = "hapticmtype";
|
static constexpr char kKeyHapticMotorType[] = "hapticmtype";
|
||||||
static constexpr char kKeyLraCalibration[] = "lra_cali";
|
static constexpr char kKeyLraCalibration[] = "lra_cali";
|
||||||
static constexpr char kKeyDbAutoIndex[] = "dbautoindex";
|
static constexpr char kKeyDbAutoIndex[] = "dbautoindex";
|
||||||
|
static constexpr char kKeyQueueRepeatMode[] = "queue_rpt";
|
||||||
static constexpr char kKeyFastCharge[] = "fastchg";
|
static constexpr char kKeyFastCharge[] = "fastchg";
|
||||||
|
|
||||||
static auto nvs_get_string(nvs_handle_t nvs, const char* key)
|
static auto nvs_get_string(nvs_handle_t nvs, const char* key)
|
||||||
@@ -278,6 +279,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
|
|||||||
bt_preferred_(kKeyBluetoothPreferred),
|
bt_preferred_(kKeyBluetoothPreferred),
|
||||||
bt_names_(kKeyBluetoothNames),
|
bt_names_(kKeyBluetoothNames),
|
||||||
db_auto_index_(kKeyDbAutoIndex),
|
db_auto_index_(kKeyDbAutoIndex),
|
||||||
|
queue_repeat_mode_(kKeyQueueRepeatMode),
|
||||||
bt_volumes_(),
|
bt_volumes_(),
|
||||||
bt_volumes_dirty_(false) {}
|
bt_volumes_dirty_(false) {}
|
||||||
|
|
||||||
@@ -304,6 +306,7 @@ auto NvsStorage::Read() -> void {
|
|||||||
bt_preferred_.read(handle_);
|
bt_preferred_.read(handle_);
|
||||||
bt_names_.read(handle_);
|
bt_names_.read(handle_);
|
||||||
db_auto_index_.read(handle_);
|
db_auto_index_.read(handle_);
|
||||||
|
queue_repeat_mode_.read(handle_);
|
||||||
readBtVolumes();
|
readBtVolumes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,6 +328,7 @@ auto NvsStorage::Write() -> bool {
|
|||||||
bt_preferred_.write(handle_);
|
bt_preferred_.write(handle_);
|
||||||
bt_names_.write(handle_);
|
bt_names_.write(handle_);
|
||||||
db_auto_index_.write(handle_);
|
db_auto_index_.write(handle_);
|
||||||
|
queue_repeat_mode_.write(handle_);
|
||||||
writeBtVolumes();
|
writeBtVolumes();
|
||||||
return nvs_commit(handle_) == ESP_OK;
|
return nvs_commit(handle_) == ESP_OK;
|
||||||
}
|
}
|
||||||
@@ -566,6 +570,16 @@ auto NvsStorage::PrimaryInput(InputModes mode) -> void {
|
|||||||
input_mode_.set(static_cast<uint8_t>(mode));
|
input_mode_.set(static_cast<uint8_t>(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto NvsStorage::QueueRepeatMode() -> uint8_t {
|
||||||
|
std::lock_guard<std::mutex> lock{mutex_};
|
||||||
|
return queue_repeat_mode_.get().value_or(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NvsStorage::QueueRepeatMode(uint8_t mode) -> void {
|
||||||
|
std::lock_guard<std::mutex> lock{mutex_};
|
||||||
|
queue_repeat_mode_.set(mode);
|
||||||
|
}
|
||||||
|
|
||||||
auto NvsStorage::DbAutoIndex() -> bool {
|
auto NvsStorage::DbAutoIndex() -> bool {
|
||||||
std::lock_guard<std::mutex> lock{mutex_};
|
std::lock_guard<std::mutex> lock{mutex_};
|
||||||
return db_auto_index_.get().value_or(true);
|
return db_auto_index_.get().value_or(true);
|
||||||
|
|||||||
@@ -38,25 +38,26 @@ namespace audio {
|
|||||||
using Reason = QueueUpdate::Reason;
|
using Reason = QueueUpdate::Reason;
|
||||||
|
|
||||||
RandomIterator::RandomIterator()
|
RandomIterator::RandomIterator()
|
||||||
: seed_(0), pos_(0), size_(0), replay_(false) {}
|
: seed_(0), pos_(0), size_(0) {}
|
||||||
|
|
||||||
RandomIterator::RandomIterator(size_t size)
|
RandomIterator::RandomIterator(size_t size)
|
||||||
: seed_(), pos_(0), size_(size), replay_(false) {
|
: seed_(), pos_(0), size_(size) {
|
||||||
esp_fill_random(&seed_, sizeof(seed_));
|
esp_fill_random(&seed_, sizeof(seed_));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RandomIterator::current() const -> size_t {
|
auto RandomIterator::current() const -> size_t {
|
||||||
if (pos_ < size_ || replay_) {
|
return MillerShuffle(pos_, seed_, size_);
|
||||||
return MillerShuffle(pos_, seed_, size_);
|
|
||||||
}
|
|
||||||
return size_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RandomIterator::next() -> void {
|
auto RandomIterator::next(bool repeat) -> bool {
|
||||||
// MillerShuffle behaves well with pos > size, returning different
|
// MillerShuffle behaves well with pos > size, returning different
|
||||||
// permutations each 'cycle'. We therefore don't need to worry about wrapping
|
// permutations each 'cycle'. We therefore don't need to worry about wrapping
|
||||||
// this value.
|
// this value.
|
||||||
pos_++;
|
if (pos_ < size_ - 1 || repeat) {
|
||||||
|
pos_++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RandomIterator::prev() -> void {
|
auto RandomIterator::prev() -> void {
|
||||||
@@ -72,10 +73,6 @@ auto RandomIterator::resize(size_t s) -> void {
|
|||||||
pos_ = 0;
|
pos_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RandomIterator::replay(bool r) -> void {
|
|
||||||
replay_ = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto notifyChanged(bool current_changed, Reason reason) -> void {
|
auto notifyChanged(bool current_changed, Reason reason) -> void {
|
||||||
QueueUpdate ev{
|
QueueUpdate ev{
|
||||||
.current_changed = current_changed,
|
.current_changed = current_changed,
|
||||||
@@ -95,15 +92,15 @@ auto notifyPlayFrom(uint32_t start_from_position) -> void {
|
|||||||
events::Audio().Dispatch(ev);
|
events::Audio().Dispatch(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db)
|
TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db, drivers::NvsStorage& nvs)
|
||||||
: mutex_(),
|
: mutex_(),
|
||||||
bg_worker_(bg_worker),
|
bg_worker_(bg_worker),
|
||||||
db_(db),
|
db_(db),
|
||||||
|
nvs_(nvs),
|
||||||
playlist_(".queue.playlist"),
|
playlist_(".queue.playlist"),
|
||||||
position_(0),
|
position_(0),
|
||||||
shuffle_(),
|
shuffle_(),
|
||||||
repeat_(false),
|
repeatMode_(static_cast<RepeatMode>(nvs.QueueRepeatMode())) {}
|
||||||
replay_(false) {}
|
|
||||||
|
|
||||||
auto TrackQueue::current() const -> TrackItem {
|
auto TrackQueue::current() const -> TrackItem {
|
||||||
const std::shared_lock<std::shared_mutex> lock(mutex_);
|
const std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||||
@@ -293,26 +290,25 @@ auto TrackQueue::goTo(size_t position) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto TrackQueue::next(Reason r) -> void {
|
auto TrackQueue::next(Reason r) -> void {
|
||||||
bool changed = true;
|
bool changed = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::unique_lock<std::shared_mutex> lock(mutex_);
|
const std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||||
auto pos = position_;
|
|
||||||
|
|
||||||
if (shuffle_) {
|
if (shuffle_) {
|
||||||
shuffle_->next();
|
changed = shuffle_->next(repeatMode_ == RepeatMode::REPEAT_QUEUE);
|
||||||
position_ = shuffle_->current();
|
position_ = shuffle_->current();
|
||||||
} else {
|
} else {
|
||||||
if (position_ + 1 < totalSize()) {
|
if (position_ + 1 < totalSize()) {
|
||||||
position_++; // Next track
|
position_++; // Next track
|
||||||
}
|
changed = true;
|
||||||
else {
|
} else if (repeatMode_ == RepeatMode::REPEAT_QUEUE) {
|
||||||
position_ = 0; // Go to beginning
|
position_ = 0; // Go to beginning
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goTo(position_);
|
goTo(position_);
|
||||||
changed = pos != position_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyChanged(changed, r);
|
notifyChanged(changed, r);
|
||||||
@@ -329,9 +325,8 @@ auto TrackQueue::previous() -> void {
|
|||||||
} else {
|
} else {
|
||||||
if (position_ > 0) {
|
if (position_ > 0) {
|
||||||
position_--;
|
position_--;
|
||||||
}
|
} else if (repeatMode_ == RepeatMode::REPEAT_QUEUE) {
|
||||||
else {
|
position_ = totalSize()-1; // Go to the end of the queue
|
||||||
position_ = totalSize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
goTo(position_);
|
goTo(position_);
|
||||||
@@ -341,7 +336,7 @@ auto TrackQueue::previous() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto TrackQueue::finish() -> void {
|
auto TrackQueue::finish() -> void {
|
||||||
if (repeat_) {
|
if (repeatMode_ == RepeatMode::REPEAT_TRACK) {
|
||||||
notifyChanged(true, Reason::kRepeatingLastTrack);
|
notifyChanged(true, Reason::kRepeatingLastTrack);
|
||||||
} else {
|
} else {
|
||||||
next(Reason::kTrackFinished);
|
next(Reason::kTrackFinished);
|
||||||
@@ -367,7 +362,6 @@ auto TrackQueue::random(bool en) -> void {
|
|||||||
const std::unique_lock<std::shared_mutex> lock(mutex_);
|
const std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||||
if (en) {
|
if (en) {
|
||||||
shuffle_.emplace(totalSize());
|
shuffle_.emplace(totalSize());
|
||||||
shuffle_->replay(replay_);
|
|
||||||
} else {
|
} else {
|
||||||
shuffle_.reset();
|
shuffle_.reset();
|
||||||
}
|
}
|
||||||
@@ -382,34 +376,14 @@ auto TrackQueue::random() const -> bool {
|
|||||||
return shuffle_.has_value();
|
return shuffle_.has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TrackQueue::repeat(bool en) -> void {
|
auto TrackQueue::repeatMode(RepeatMode mode) -> void {
|
||||||
{
|
repeatMode_ = mode;
|
||||||
const std::unique_lock<std::shared_mutex> lock(mutex_);
|
nvs_.QueueRepeatMode(repeatMode_);
|
||||||
repeat_ = en;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyChanged(false, Reason::kExplicitUpdate);
|
notifyChanged(false, Reason::kExplicitUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TrackQueue::repeat() const -> bool {
|
auto TrackQueue::repeatMode() const -> RepeatMode {
|
||||||
const std::shared_lock<std::shared_mutex> lock(mutex_);
|
return repeatMode_;
|
||||||
return repeat_;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto TrackQueue::replay(bool en) -> void {
|
|
||||||
{
|
|
||||||
const std::unique_lock<std::shared_mutex> lock(mutex_);
|
|
||||||
replay_ = en;
|
|
||||||
if (shuffle_) {
|
|
||||||
shuffle_->replay(en);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notifyChanged(false, Reason::kExplicitUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto TrackQueue::replay() const -> bool {
|
|
||||||
const std::shared_lock<std::shared_mutex> lock(mutex_);
|
|
||||||
return replay_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TrackQueue::serialise() -> std::string {
|
auto TrackQueue::serialise() -> std::string {
|
||||||
@@ -417,9 +391,8 @@ auto TrackQueue::serialise() -> std::string {
|
|||||||
cppbor::Map encoded;
|
cppbor::Map encoded;
|
||||||
|
|
||||||
cppbor::Array metadata{
|
cppbor::Array metadata{
|
||||||
cppbor::Bool{repeat_},
|
|
||||||
cppbor::Bool{replay_},
|
|
||||||
cppbor::Uint{position_},
|
cppbor::Uint{position_},
|
||||||
|
cppbor::Uint{repeatMode_},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (opened_playlist_) {
|
if (opened_playlist_) {
|
||||||
@@ -471,26 +444,24 @@ cppbor::ParseClient* TrackQueue::QueueParseClient::item(
|
|||||||
i_ = 0;
|
i_ = 0;
|
||||||
} else if (item->type() == cppbor::UINT) {
|
} else if (item->type() == cppbor::UINT) {
|
||||||
auto val = item->asUint()->unsignedValue();
|
auto val = item->asUint()->unsignedValue();
|
||||||
// Save the position so we can apply it later when we have finished
|
if (i_ == 0) {
|
||||||
// serialising
|
// First value == position
|
||||||
position_to_set_ = val;
|
// Save the position so we can apply it later when we have finished
|
||||||
|
// serialising
|
||||||
|
position_to_set_ = val;
|
||||||
|
} else if (i_ == 1) {
|
||||||
|
// Second value == repeat mode
|
||||||
|
queue_.repeatMode_ = static_cast<RepeatMode>(val);
|
||||||
|
}
|
||||||
|
i_++;
|
||||||
} else if (item->type() == cppbor::TSTR) {
|
} else if (item->type() == cppbor::TSTR) {
|
||||||
auto val = item->asTstr();
|
auto val = item->asTstr();
|
||||||
queue_.openPlaylist(val->value(), false);
|
queue_.openPlaylist(val->value(), false);
|
||||||
} else if (item->type() == cppbor::SIMPLE) {
|
|
||||||
bool val = item->asBool()->value();
|
|
||||||
if (i_ == 0) {
|
|
||||||
queue_.repeat_ = val;
|
|
||||||
} else if (i_ == 1) {
|
|
||||||
queue_.replay_ = val;
|
|
||||||
}
|
|
||||||
i_++;
|
|
||||||
}
|
}
|
||||||
} else if (state_ == State::kShuffle) {
|
} else if (state_ == State::kShuffle) {
|
||||||
if (item->type() == cppbor::ARRAY) {
|
if (item->type() == cppbor::ARRAY) {
|
||||||
i_ = 0;
|
i_ = 0;
|
||||||
queue_.shuffle_.emplace();
|
queue_.shuffle_.emplace();
|
||||||
queue_.shuffle_->replay(queue_.replay_);
|
|
||||||
} else if (item->type() == cppbor::UINT) {
|
} else if (item->type() == cppbor::UINT) {
|
||||||
auto val = item->asUint()->unsignedValue();
|
auto val = item->asUint()->unsignedValue();
|
||||||
switch (i_) {
|
switch (i_) {
|
||||||
|
|||||||
@@ -32,12 +32,11 @@ class RandomIterator {
|
|||||||
|
|
||||||
auto current() const -> size_t;
|
auto current() const -> size_t;
|
||||||
|
|
||||||
auto next() -> void;
|
auto next(bool repeat) -> bool;
|
||||||
auto prev() -> void;
|
auto prev() -> void;
|
||||||
|
|
||||||
// Note resizing has the side-effect of restarting iteration.
|
// Note resizing has the side-effect of restarting iteration.
|
||||||
auto resize(size_t) -> void;
|
auto resize(size_t) -> void;
|
||||||
auto replay(bool) -> void;
|
|
||||||
|
|
||||||
auto seed() -> size_t& { return seed_; }
|
auto seed() -> size_t& { return seed_; }
|
||||||
auto pos() -> size_t& { return pos_; }
|
auto pos() -> size_t& { return pos_; }
|
||||||
@@ -47,7 +46,6 @@ class RandomIterator {
|
|||||||
size_t seed_;
|
size_t seed_;
|
||||||
size_t pos_;
|
size_t pos_;
|
||||||
size_t size_;
|
size_t size_;
|
||||||
bool replay_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -65,7 +63,7 @@ class RandomIterator {
|
|||||||
*/
|
*/
|
||||||
class TrackQueue {
|
class TrackQueue {
|
||||||
public:
|
public:
|
||||||
TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db);
|
TrackQueue(tasks::WorkerPool& bg_worker, database::Handle db, drivers::NvsStorage& nvs);
|
||||||
|
|
||||||
/* Returns the currently playing track. */
|
/* Returns the currently playing track. */
|
||||||
using TrackItem =
|
using TrackItem =
|
||||||
@@ -107,11 +105,14 @@ class TrackQueue {
|
|||||||
auto random(bool) -> void;
|
auto random(bool) -> void;
|
||||||
auto random() const -> bool;
|
auto random() const -> bool;
|
||||||
|
|
||||||
auto repeat(bool) -> void;
|
enum RepeatMode {
|
||||||
auto repeat() const -> bool;
|
OFF = 0,
|
||||||
|
REPEAT_TRACK = 1,
|
||||||
|
REPEAT_QUEUE = 2,
|
||||||
|
};
|
||||||
|
|
||||||
auto replay(bool) -> void;
|
auto repeatMode(RepeatMode mode) -> void;
|
||||||
auto replay() const -> bool;
|
auto repeatMode() const -> RepeatMode;
|
||||||
|
|
||||||
auto serialise() -> std::string;
|
auto serialise() -> std::string;
|
||||||
auto deserialise(const std::string&) -> void;
|
auto deserialise(const std::string&) -> void;
|
||||||
@@ -129,6 +130,7 @@ class TrackQueue {
|
|||||||
|
|
||||||
tasks::WorkerPool& bg_worker_;
|
tasks::WorkerPool& bg_worker_;
|
||||||
database::Handle db_;
|
database::Handle db_;
|
||||||
|
drivers::NvsStorage& nvs_;
|
||||||
|
|
||||||
MutablePlaylist playlist_;
|
MutablePlaylist playlist_;
|
||||||
std::optional<Playlist> opened_playlist_;
|
std::optional<Playlist> opened_playlist_;
|
||||||
@@ -136,8 +138,7 @@ class TrackQueue {
|
|||||||
size_t position_;
|
size_t position_;
|
||||||
|
|
||||||
std::optional<RandomIterator> shuffle_;
|
std::optional<RandomIterator> shuffle_;
|
||||||
bool repeat_;
|
RepeatMode repeatMode_;
|
||||||
bool replay_;
|
|
||||||
|
|
||||||
class QueueParseClient : public cppbor::ParseClient {
|
class QueueParseClient : public cppbor::ParseClient {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -101,6 +101,24 @@ static const struct luaL_Reg kQueueFuncs[] = {
|
|||||||
|
|
||||||
static auto lua_queue(lua_State* state) -> int {
|
static auto lua_queue(lua_State* state) -> int {
|
||||||
luaL_newlib(state, kQueueFuncs);
|
luaL_newlib(state, kQueueFuncs);
|
||||||
|
|
||||||
|
// Repeat Mode Enum
|
||||||
|
lua_pushliteral(state, "RepeatMode");
|
||||||
|
lua_newtable(state);
|
||||||
|
|
||||||
|
lua_pushliteral(state, "OFF");
|
||||||
|
lua_pushinteger(state, (int)audio::TrackQueue::RepeatMode::OFF);
|
||||||
|
lua_rawset(state, -3);
|
||||||
|
|
||||||
|
lua_pushliteral(state, "REPEAT_TRACK");
|
||||||
|
lua_pushinteger(state, (int)audio::TrackQueue::RepeatMode::REPEAT_TRACK);
|
||||||
|
lua_rawset(state, -3);
|
||||||
|
|
||||||
|
lua_pushliteral(state, "REPEAT_QUEUE");
|
||||||
|
lua_pushinteger(state, (int)audio::TrackQueue::RepeatMode::REPEAT_QUEUE);
|
||||||
|
lua_rawset(state, -3);
|
||||||
|
|
||||||
|
lua_rawset(state, -3);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ auto Booting::entry() -> void {
|
|||||||
sServices->samd(), std::unique_ptr<drivers::AdcBattery>(adc)));
|
sServices->samd(), std::unique_ptr<drivers::AdcBattery>(adc)));
|
||||||
|
|
||||||
sServices->track_queue(std::make_unique<audio::TrackQueue>(
|
sServices->track_queue(std::make_unique<audio::TrackQueue>(
|
||||||
sServices->bg_worker(), sServices->database()));
|
sServices->bg_worker(), sServices->database(), sServices->nvs()));
|
||||||
sServices->tag_parser(std::make_unique<database::TagParserImpl>());
|
sServices->tag_parser(std::make_unique<database::TagParserImpl>());
|
||||||
sServices->collator(locale::CreateCollator());
|
sServices->collator(locale::CreateCollator());
|
||||||
sServices->tts(std::make_unique<tts::Provider>());
|
sServices->tts(std::make_unique<tts::Provider>());
|
||||||
|
|||||||
+11
-26
@@ -210,20 +210,15 @@ lua::Property UiState::sQueuePosition{0, [](const lua::LuaValue& val){
|
|||||||
return sServices->track_queue().currentPosition(new_val-1);
|
return sServices->track_queue().currentPosition(new_val-1);
|
||||||
}};
|
}};
|
||||||
lua::Property UiState::sQueueSize{0};
|
lua::Property UiState::sQueueSize{0};
|
||||||
lua::Property UiState::sQueueRepeat{false, [](const lua::LuaValue& val) {
|
lua::Property UiState::sQueueRepeatMode{0, [](const lua::LuaValue& val) {
|
||||||
if (!std::holds_alternative<bool>(val)) {
|
if (!std::holds_alternative<int>(val)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool new_val = std::get<bool>(val);
|
int new_val = std::get<int>(val);
|
||||||
sServices->track_queue().repeat(new_val);
|
if (new_val < 0 || new_val >= 3) {
|
||||||
return true;
|
|
||||||
}};
|
|
||||||
lua::Property UiState::sQueueReplay{false, [](const lua::LuaValue& val) {
|
|
||||||
if (!std::holds_alternative<bool>(val)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool new_val = std::get<bool>(val);
|
sServices->track_queue().repeatMode(static_cast<audio::TrackQueue::RepeatMode>(new_val));
|
||||||
sServices->track_queue().replay(new_val);
|
|
||||||
return true;
|
return true;
|
||||||
}};
|
}};
|
||||||
lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) {
|
lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) {
|
||||||
@@ -450,8 +445,7 @@ void UiState::react(const audio::QueueUpdate& update) {
|
|||||||
}
|
}
|
||||||
sQueuePosition.setDirect(current_pos);
|
sQueuePosition.setDirect(current_pos);
|
||||||
sQueueRandom.setDirect(queue.random());
|
sQueueRandom.setDirect(queue.random());
|
||||||
sQueueRepeat.setDirect(queue.repeat());
|
sQueueRepeatMode.setDirect(queue.repeatMode());
|
||||||
sQueueReplay.setDirect(queue.replay());
|
|
||||||
|
|
||||||
if (update.reason == audio::QueueUpdate::Reason::kBulkLoadingUpdate) {
|
if (update.reason == audio::QueueUpdate::Reason::kBulkLoadingUpdate) {
|
||||||
sQueueLoading.setDirect(true);
|
sQueueLoading.setDirect(true);
|
||||||
@@ -654,8 +648,7 @@ void Lua::entry() {
|
|||||||
{"previous", [&](lua_State* s) { return QueuePrevious(s); }},
|
{"previous", [&](lua_State* s) { return QueuePrevious(s); }},
|
||||||
{"position", &sQueuePosition},
|
{"position", &sQueuePosition},
|
||||||
{"size", &sQueueSize},
|
{"size", &sQueueSize},
|
||||||
{"replay", &sQueueReplay},
|
{"repeat_mode", &sQueueRepeatMode},
|
||||||
{"repeat_track", &sQueueRepeat},
|
|
||||||
{"random", &sQueueRandom},
|
{"random", &sQueueRandom},
|
||||||
{"loading", &sQueueLoading},
|
{"loading", &sQueueLoading},
|
||||||
});
|
});
|
||||||
@@ -850,23 +843,15 @@ auto Lua::SetRandom(const lua::LuaValue& val) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Lua::SetRepeat(const lua::LuaValue& val) -> bool {
|
auto Lua::SetRepeatMode(const lua::LuaValue& val) -> bool {
|
||||||
if (!std::holds_alternative<bool>(val)) {
|
if (!std::holds_alternative<int>(val)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool b = std::get<bool>(val);
|
int mode = std::get<int>(val);
|
||||||
sServices->track_queue().repeat(b);
|
sServices->track_queue().repeatMode(static_cast<audio::TrackQueue::RepeatMode>(mode));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Lua::SetReplay(const lua::LuaValue& val) -> bool {
|
|
||||||
if (!std::holds_alternative<bool>(val)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool b = std::get<bool>(val);
|
|
||||||
sServices->track_queue().replay(b);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lua::exit() {
|
void Lua::exit() {
|
||||||
lv_group_set_default(NULL);
|
lv_group_set_default(NULL);
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
|
|||||||
|
|
||||||
static lua::Property sQueuePosition;
|
static lua::Property sQueuePosition;
|
||||||
static lua::Property sQueueSize;
|
static lua::Property sQueueSize;
|
||||||
static lua::Property sQueueReplay;
|
static lua::Property sQueueRepeatMode;
|
||||||
static lua::Property sQueueRepeat;
|
|
||||||
static lua::Property sQueueRandom;
|
static lua::Property sQueueRandom;
|
||||||
static lua::Property sQueueLoading;
|
static lua::Property sQueueLoading;
|
||||||
|
|
||||||
@@ -177,8 +176,7 @@ class Lua : public UiState {
|
|||||||
|
|
||||||
auto SetPlaying(const lua::LuaValue&) -> bool;
|
auto SetPlaying(const lua::LuaValue&) -> bool;
|
||||||
auto SetRandom(const lua::LuaValue&) -> bool;
|
auto SetRandom(const lua::LuaValue&) -> bool;
|
||||||
auto SetRepeat(const lua::LuaValue&) -> bool;
|
auto SetRepeatMode(const lua::LuaValue&) -> bool;
|
||||||
auto SetReplay(const lua::LuaValue&) -> bool;
|
|
||||||
|
|
||||||
auto QueueNext(lua_State*) -> int;
|
auto QueueNext(lua_State*) -> int;
|
||||||
auto QueuePrevious(lua_State*) -> int;
|
auto QueuePrevious(lua_State*) -> int;
|
||||||
|
|||||||
Reference in New Issue
Block a user