diff --git a/NEWS b/NEWS index 08266f358..4693f94c2 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ ver 0.24 (not yet released) - wavpack: require libwavpack version 5 * player - add option "mixramp_analyzer" to scan MixRamp tags on-the-fly + - "one-shot" consume mode * tags - new tags "TitleSort", "Mood" * switch to C++20 diff --git a/meson.build b/meson.build index d3c5a76fa..469c77fda 100644 --- a/meson.build +++ b/meson.build @@ -468,6 +468,7 @@ basic = static_library( 'basic', 'src/ReplayGainMode.cxx', 'src/SingleMode.cxx', + 'src/ConsumeMode.cxx', include_directories: inc, ) diff --git a/src/ConsumeMode.cxx b/src/ConsumeMode.cxx new file mode 100644 index 000000000..433bd06e8 --- /dev/null +++ b/src/ConsumeMode.cxx @@ -0,0 +1,59 @@ +/* + * Copyright 2003-2022 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ConsumeMode.hxx" +#include "util/Compiler.h" + +#include +#include + +#include + +const char * +ConsumeToString(ConsumeMode mode) noexcept +{ + switch (mode) { + case ConsumeMode::OFF: + return "0"; + + case ConsumeMode::ON: + return "1"; + + case ConsumeMode::ONE_SHOT: + return "oneshot"; + } + + assert(false); + gcc_unreachable(); +} + +ConsumeMode +ConsumeFromString(const char *s) +{ + assert(s != nullptr); + + if (strcmp(s, "0") == 0) + return ConsumeMode::OFF; + else if (strcmp(s, "1") == 0) + return ConsumeMode::ON; + else if (strcmp(s, "oneshot") == 0) + return ConsumeMode::ONE_SHOT; + else + throw std::invalid_argument("Unrecognized consume mode, expected 0, 1, or oneshot"); +} diff --git a/src/ConsumeMode.hxx b/src/ConsumeMode.hxx new file mode 100644 index 000000000..75048f84e --- /dev/null +++ b/src/ConsumeMode.hxx @@ -0,0 +1,44 @@ +/* + * Copyright 2003-2022 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONSUME_MODE_HXX +#define MPD_CONSUME_MODE_HXX + +#include + +enum class ConsumeMode : uint8_t { + OFF, + ON, + ONE_SHOT, +}; + +/** + * Return the string representation of a #ConsumeMode. + */ +[[gnu::const]] +const char * +ConsumeToString(ConsumeMode mode) noexcept; + +/** + * Parse a string to a #ConsumeMode. Throws std::invalid_argument on error. + */ +ConsumeMode +ConsumeFromString(const char *s); + +#endif diff --git a/src/Partition.hxx b/src/Partition.hxx index 6f9de5931..04b826a1e 100644 --- a/src/Partition.hxx +++ b/src/Partition.hxx @@ -31,6 +31,7 @@ #include "protocol/RangeArg.hxx" #include "ReplayGainMode.hxx" #include "SingleMode.hxx" +#include "ConsumeMode.hxx" #include "Chrono.hxx" #include "config.h" @@ -218,7 +219,7 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { playlist.SetSingle(pc, new_value); } - void SetConsume(bool new_value) noexcept { + void SetConsume(ConsumeMode new_value) noexcept { playlist.SetConsume(new_value); } diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index 9d8138288..1216a98b9 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -22,6 +22,7 @@ #include "Request.hxx" #include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" +#include "ConsumeMode.hxx" #include "SingleMode.hxx" #include "client/Client.hxx" #include "client/Response.hxx" @@ -146,7 +147,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r) (unsigned)playlist.GetRepeat(), (unsigned)playlist.GetRandom(), SingleToString(playlist.GetSingle()), - (unsigned)playlist.GetConsume(), + ConsumeToString(playlist.GetConsume()), partition.name.c_str(), playlist.GetVersion(), playlist.GetLength(), @@ -260,8 +261,8 @@ handle_single(Client &client, Request args, [[maybe_unused]] Response &r) CommandResult handle_consume(Client &client, Request args, [[maybe_unused]] Response &r) { - bool status = args.ParseBool(0); - client.GetPartition().SetConsume(status); + auto new_mode = ConsumeFromString(args.front()); + client.GetPartition().SetConsume(new_mode); return CommandResult::OK; } diff --git a/src/queue/Playlist.cxx b/src/queue/Playlist.cxx index 4c9241386..4b2ab75a1 100644 --- a/src/queue/Playlist.cxx +++ b/src/queue/Playlist.cxx @@ -23,6 +23,7 @@ #include "player/Control.hxx" #include "song/DetachedSong.hxx" #include "SingleMode.hxx" +#include "ConsumeMode.hxx" #include "Log.hxx" #include @@ -101,9 +102,14 @@ playlist::QueuedSongStarted(PlayerControl &pc) noexcept current = queued; queued = -1; - if (queue.consume) + if (queue.consume != ConsumeMode::OFF) DeleteOrder(pc, old_current); + if (queue.consume == ConsumeMode::ONE_SHOT) { + queue.consume = ConsumeMode::OFF; + listener.OnQueueOptionsChanged(); + } + listener.OnQueueSongStarted(); SongStarted(); @@ -289,12 +295,13 @@ playlist::SetSingle(PlayerControl &pc, SingleMode status) noexcept } void -playlist::SetConsume(bool status) noexcept +playlist::SetConsume(ConsumeMode status) noexcept { if (status == queue.consume) return; queue.consume = status; + listener.OnQueueOptionsChanged(); } diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx index 88b1800dd..cb82bcd92 100644 --- a/src/queue/Playlist.hxx +++ b/src/queue/Playlist.hxx @@ -21,6 +21,7 @@ #define MPD_PLAYLIST_HXX #include "SingleMode.hxx" +#include "ConsumeMode.hxx" #include "queue/Queue.hxx" #include "config.h" @@ -369,11 +370,11 @@ public: void SetSingle(PlayerControl &pc, SingleMode new_value) noexcept; - bool GetConsume() const noexcept { + ConsumeMode GetConsume() const noexcept { return queue.consume; } - void SetConsume(bool new_value) noexcept; + void SetConsume(ConsumeMode new_value) noexcept; private: /** diff --git a/src/queue/PlaylistControl.cxx b/src/queue/PlaylistControl.cxx index 204be8457..fd3ab1fd4 100644 --- a/src/queue/PlaylistControl.cxx +++ b/src/queue/PlaylistControl.cxx @@ -26,6 +26,7 @@ #include "PlaylistError.hxx" #include "player/Control.hxx" #include "song/DetachedSong.hxx" +#include "Listener.hxx" #include "Log.hxx" void @@ -178,8 +179,14 @@ playlist::PlayNext(PlayerControl &pc) } /* Consume mode removes each played songs. */ - if (queue.consume) + if (queue.consume != ConsumeMode::OFF) DeleteOrder(pc, old_current); + + /* Disable consume mode after consuming one song in oneshot mode. */ + if (queue.consume == ConsumeMode::ONE_SHOT) { + queue.consume = ConsumeMode::OFF; + listener.OnQueueOptionsChanged(); + } } void diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx index 41f9d1dc9..9e245df7d 100644 --- a/src/queue/PlaylistState.cxx +++ b/src/queue/PlaylistState.cxx @@ -162,7 +162,7 @@ playlist_state_restore(const StateFileConfig &config, } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) { playlist.SetSingle(pc, SingleFromString(p)); } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CONSUME))) { - playlist.SetConsume(StringIsEqual(p, "1")); + playlist.SetConsume(ConsumeFromString(p)); } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) { pc.SetCrossFade(FloatDuration(atoi(p))); } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) { @@ -243,6 +243,7 @@ playlist_state_get_hash(const playlist &playlist, ((int)playlist.queue.single << 25) ^ (playlist.queue.random << 27) ^ (playlist.queue.repeat << 28) ^ - (playlist.queue.consume << 30) ^ + /* note that this takes 2 bits */ + ((int)playlist.queue.consume << 29) ^ (playlist.queue.random << 31); } diff --git a/src/queue/Queue.cxx b/src/queue/Queue.cxx index ea2f96ca4..0ac91f308 100644 --- a/src/queue/Queue.cxx +++ b/src/queue/Queue.cxx @@ -54,11 +54,11 @@ Queue::GetNextOrder(unsigned _order) const noexcept { assert(_order < length); - if (single != SingleMode::OFF && repeat && !consume) + if (single != SingleMode::OFF && repeat && consume == ConsumeMode::OFF ) return _order; else if (_order + 1 < length) return _order + 1; - else if (repeat && (_order > 0 || !consume)) + else if (repeat && (_order > 0 || consume == ConsumeMode::OFF)) /* restart at first song */ return 0; else diff --git a/src/queue/Queue.hxx b/src/queue/Queue.hxx index 1d44ec45f..480a17eee 100644 --- a/src/queue/Queue.hxx +++ b/src/queue/Queue.hxx @@ -23,6 +23,7 @@ #include "util/Compiler.h" #include "IdTable.hxx" #include "SingleMode.hxx" +#include "ConsumeMode.hxx" #include "util/LazyRandomEngine.hxx" #include @@ -96,7 +97,7 @@ struct Queue { SingleMode single = SingleMode::OFF; /** remove each played files. */ - bool consume = false; + ConsumeMode consume = ConsumeMode::OFF; /** play back songs in random order? */ bool random = false;