diff --git a/NEWS b/NEWS index 82dcdaa52..ab5139a5b 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ ver 0.19 (not yet released) * protocol - - new commands "addtagid", "cleartagid", "listfiles" + - new commands "addtagid", "cleartagid", "listfiles", "rangeid" - "lsinfo" and "readcomments" allowed for remote files - "listneighbors" lists file servers on the local network - "playlistadd" supports file:/// diff --git a/doc/protocol.xml b/doc/protocol.xml index 360d2d22a..cec92e180 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -1213,6 +1213,28 @@ OK + + + + rangeid + ID + START:END + + + + + Since MPD + 0.19 Specifies the portion of the + song that shall be played. START and + END are offsets in seconds + (fractional seconds allowed); both are optional. + Omitting both (i.e. sending just ":") means "remove the + range, play everything". A song that is currently + playing cannot be manipulated this way. + + + + diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 6143dacdf..d29950eeb 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -150,6 +150,7 @@ static const struct command commands[] = { { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid }, { "readcomments", PERMISSION_READ, 1, 1, handle_read_comments }, { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index 1ff7a732b..c99a6687a 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -34,6 +34,7 @@ #include "ls.hxx" #include "util/ConstBuffer.hxx" #include "util/UriUtil.hxx" +#include "util/NumberParser.hxx" #include "util/Error.hxx" #include "fs/AllocatedPath.hxx" @@ -118,6 +119,59 @@ handle_addid(Client &client, unsigned argc, char *argv[]) return CommandResult::OK; } +/** + * Parse a string in the form "START:END", both being (optional) + * fractional non-negative time offsets in seconds. Returns both in + * integer milliseconds. Omitted values are zero. + */ +static bool +parse_time_range(const char *p, unsigned &start_ms, unsigned &end_ms) +{ + char *endptr; + + const float start = ParseFloat(p, &endptr); + if (*endptr != ':' || start < 0) + return false; + + start_ms = endptr > p + ? unsigned(start * 1000u) + : 0u; + + p = endptr + 1; + + const float end = ParseFloat(p, &endptr); + if (*endptr != 0 || end < 0) + return false; + + end_ms = endptr > p + ? unsigned(end * 1000u) + : 0u; + + return end_ms == 0 || end_ms > start_ms; +} + +CommandResult +handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return CommandResult::ERROR; + + unsigned start_ms, end_ms; + if (!parse_time_range(argv[2], start_ms, end_ms)) { + command_error(client, ACK_ERROR_ARG, "Bad range"); + return CommandResult::ERROR; + } + + Error error; + if (!client.partition.playlist.SetSongIdRange(client.partition.pc, + id, start_ms, end_ms, + error)) + return print_error(client, error); + + return CommandResult::OK; +} + CommandResult handle_delete(Client &client, gcc_unused unsigned argc, char *argv[]) { diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx index ece543cfd..f98f7bad2 100644 --- a/src/command/QueueCommands.hxx +++ b/src/command/QueueCommands.hxx @@ -30,6 +30,9 @@ handle_add(Client &client, unsigned argc, char *argv[]); CommandResult handle_addid(Client &client, unsigned argc, char *argv[]); +CommandResult +handle_rangeid(Client &client, unsigned argc, char *argv[]); + CommandResult handle_delete(Client &client, unsigned argc, char *argv[]); diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx index f2d778382..0f73a0513 100644 --- a/src/queue/Playlist.hxx +++ b/src/queue/Playlist.hxx @@ -225,6 +225,14 @@ public: PlaylistResult SetPriorityId(PlayerControl &pc, unsigned song_id, uint8_t priority); + /** + * Sets the start_ms and end_ms attributes on the song + * with the specified id. + */ + bool SetSongIdRange(PlayerControl &pc, unsigned id, + unsigned start_ms, unsigned end_ms, + Error &error); + bool AddSongIdTag(unsigned id, TagType tag_type, const char *value, Error &error); bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error); diff --git a/src/queue/PlaylistEdit.cxx b/src/queue/PlaylistEdit.cxx index 4804a30aa..2ac015f6f 100644 --- a/src/queue/PlaylistEdit.cxx +++ b/src/queue/PlaylistEdit.cxx @@ -431,3 +431,60 @@ playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end) UpdateQueuedSong(pc, queued_song); OnModified(); } + +bool +playlist::SetSongIdRange(PlayerControl &pc, unsigned id, + unsigned start_ms, unsigned end_ms, + Error &error) +{ + assert(end_ms == 0 || start_ms < end_ms); + + int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + if (playing) { + if (position == current) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit the current song"); + return false; + } + + if (position == queued) { + /* if we're manipulating the "queued" song, + the decoder thread may be decoding it + already; cancel that */ + pc.Cancel(); + queued = -1; + } + } + + DetachedSong &song = queue.Get(position); + if (song.GetTag().time > 0) { + /* validate the offsets */ + + const unsigned duration = song.GetTag().time; + if (start_ms / 1000u > duration) { + error.Set(playlist_domain, + int(PlaylistResult::BAD_RANGE), + "Invalid start offset"); + return false; + } + + if (end_ms / 1000u > duration) + end_ms = 0; + } + + /* edit it */ + song.SetStartMS(start_ms); + song.SetEndMS(end_ms); + + /* announce the change to all interested subsystems */ + UpdateQueuedSong(pc, nullptr); + queue.ModifyAtPosition(position); + OnModified(); + return true; +}