QueueCommands: new command "rangeid"
Manipulates the playback range of a queued song.
This commit is contained in:
parent
5ca6e2910a
commit
751995ab95
2
NEWS
2
NEWS
|
@ -1,6 +1,6 @@
|
||||||
ver 0.19 (not yet released)
|
ver 0.19 (not yet released)
|
||||||
* protocol
|
* protocol
|
||||||
- new commands "addtagid", "cleartagid", "listfiles"
|
- new commands "addtagid", "cleartagid", "listfiles", "rangeid"
|
||||||
- "lsinfo" and "readcomments" allowed for remote files
|
- "lsinfo" and "readcomments" allowed for remote files
|
||||||
- "listneighbors" lists file servers on the local network
|
- "listneighbors" lists file servers on the local network
|
||||||
- "playlistadd" supports file:///
|
- "playlistadd" supports file:///
|
||||||
|
|
|
@ -1213,6 +1213,28 @@ OK
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_rangeid">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>rangeid</command>
|
||||||
|
<arg choice="req"><replaceable>ID</replaceable></arg>
|
||||||
|
<arg choice="req"><replaceable>START:END</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<footnote id="since_0_19"><simpara>Since MPD
|
||||||
|
0.19</simpara></footnote> Specifies the portion of the
|
||||||
|
song that shall be played. <varname>START</varname> and
|
||||||
|
<varname>END</varname> 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.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry id="command_shuffle">
|
<varlistentry id="command_shuffle">
|
||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
|
|
|
@ -150,6 +150,7 @@ static const struct command commands[] = {
|
||||||
{ "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
|
{ "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
|
||||||
{ "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
|
{ "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
|
||||||
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
|
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
|
||||||
|
{ "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid },
|
||||||
{ "readcomments", PERMISSION_READ, 1, 1, handle_read_comments },
|
{ "readcomments", PERMISSION_READ, 1, 1, handle_read_comments },
|
||||||
{ "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
|
{ "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
|
||||||
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
|
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "ls.hxx"
|
#include "ls.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
|
#include "util/NumberParser.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
|
||||||
|
@ -118,6 +119,59 @@ handle_addid(Client &client, unsigned argc, char *argv[])
|
||||||
return CommandResult::OK;
|
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
|
CommandResult
|
||||||
handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
|
handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,6 +30,9 @@ handle_add(Client &client, unsigned argc, char *argv[]);
|
||||||
CommandResult
|
CommandResult
|
||||||
handle_addid(Client &client, unsigned argc, char *argv[]);
|
handle_addid(Client &client, unsigned argc, char *argv[]);
|
||||||
|
|
||||||
|
CommandResult
|
||||||
|
handle_rangeid(Client &client, unsigned argc, char *argv[]);
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
handle_delete(Client &client, unsigned argc, char *argv[]);
|
handle_delete(Client &client, unsigned argc, char *argv[]);
|
||||||
|
|
||||||
|
|
|
@ -225,6 +225,14 @@ public:
|
||||||
PlaylistResult SetPriorityId(PlayerControl &pc,
|
PlaylistResult SetPriorityId(PlayerControl &pc,
|
||||||
unsigned song_id, uint8_t priority);
|
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,
|
bool AddSongIdTag(unsigned id, TagType tag_type, const char *value,
|
||||||
Error &error);
|
Error &error);
|
||||||
bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error);
|
bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error);
|
||||||
|
|
|
@ -431,3 +431,60 @@ playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end)
|
||||||
UpdateQueuedSong(pc, queued_song);
|
UpdateQueuedSong(pc, queued_song);
|
||||||
OnModified();
|
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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue