diff --git a/NEWS b/NEWS index f7c47a7c4..6ae634e73 100644 --- a/NEWS +++ b/NEWS @@ -29,11 +29,16 @@ ver 0.22 (not yet released) * switch to C++17 - GCC 7 or clang 4 (or newer) recommended -ver 0.21.14 (not yet released) +ver 0.21.14 (2019/08/21) * decoder - sidplay: show track durations in database - sidplay: convert tag values from Windows-1252 charset - sidplay: strip text from "Date" tag +* player + - fix crash after song change + - fix seek position after restarting the decoder +* protocol + - include command name in error responses ver 0.21.13 (2019/08/06) * input diff --git a/doc/protocol.rst b/doc/protocol.rst index 72520d67e..b190d1921 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -839,7 +839,8 @@ The music database albumart foo/bar.ogg 0 size: 1024768 binary: 8192 - <8192 bytes>OK + <8192 bytes> + OK :command:`count {FILTER} [group {GROUPTYPE}]` Count the number of songs and their total playtime in diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 005d6245e..b8906f7cc 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -212,9 +212,10 @@ static constexpr struct command commands[] = { static constexpr unsigned num_commands = std::size(commands); +gcc_pure static bool command_available(gcc_unused const Partition &partition, - gcc_unused const struct command *cmd) + gcc_unused const struct command *cmd) noexcept { #ifdef ENABLE_SQLITE if (StringIsEqual(cmd->cmd, "sticker")) @@ -241,7 +242,7 @@ command_available(gcc_unused const Partition &partition, static CommandResult PrintAvailableCommands(Response &r, const Partition &partition, - unsigned permission) + unsigned permission) noexcept { for (unsigned i = 0; i < num_commands; ++i) { const struct command *cmd = &commands[i]; @@ -255,7 +256,7 @@ PrintAvailableCommands(Response &r, const Partition &partition, } static CommandResult -PrintUnavailableCommands(Response &r, unsigned permission) +PrintUnavailableCommands(Response &r, unsigned permission) noexcept { for (unsigned i = 0; i < num_commands; ++i) { const struct command *cmd = &commands[i]; @@ -282,7 +283,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r) } void -command_init() +command_init() noexcept { #ifndef NDEBUG /* ensure that the command list is sorted */ @@ -291,8 +292,9 @@ command_init() #endif } +gcc_pure static const struct command * -command_lookup(const char *name) +command_lookup(const char *name) noexcept { unsigned a = 0, b = num_commands, i; @@ -314,7 +316,7 @@ command_lookup(const char *name) static bool command_check_request(const struct command *cmd, Response &r, - unsigned permission, Request args) + unsigned permission, Request args) noexcept { if (cmd->permission != (permission & cmd->permission)) { r.FormatError(ACK_ERROR_PERMISSION, @@ -348,7 +350,7 @@ command_check_request(const struct command *cmd, Response &r, static const struct command * command_checked_lookup(Response &r, unsigned permission, - const char *cmd_name, Request args) + const char *cmd_name, Request args) noexcept { const struct command *cmd = command_lookup(cmd_name); if (cmd == nullptr) { @@ -366,8 +368,8 @@ command_checked_lookup(Response &r, unsigned permission, } CommandResult -command_process(Client &client, unsigned num, char *line) -try { +command_process(Client &client, unsigned num, char *line) noexcept +{ Response r(client, num); /* get the command name (first word on the line) */ @@ -395,34 +397,33 @@ try { char *argv[COMMAND_ARGV_MAX]; Request args(argv, 0); - /* now parse the arguments (quoted or unquoted) */ + try { + /* now parse the arguments (quoted or unquoted) */ - while (true) { - if (args.size == COMMAND_ARGV_MAX) { - r.Error(ACK_ERROR_ARG, "Too many arguments"); - return CommandResult::ERROR; + while (true) { + if (args.size == COMMAND_ARGV_MAX) { + r.Error(ACK_ERROR_ARG, "Too many arguments"); + return CommandResult::ERROR; + } + + char *a = tokenizer.NextParam(); + if (a == nullptr) + break; + + argv[args.size++] = a; } - char *a = tokenizer.NextParam(); - if (a == nullptr) - break; + /* look up and invoke the command handler */ - argv[args.size++] = a; + const struct command *cmd = + command_checked_lookup(r, client.GetPermission(), + cmd_name, args); + if (cmd == nullptr) + return CommandResult::ERROR; + + return cmd->handler(client, args, r); + } catch (...) { + PrintError(r, std::current_exception()); + return CommandResult::ERROR; } - - /* look up and invoke the command handler */ - - const struct command *cmd = - command_checked_lookup(r, client.GetPermission(), - cmd_name, args); - - CommandResult ret = cmd - ? cmd->handler(client, args, r) - : CommandResult::ERROR; - - return ret; -} catch (const std::exception &e) { - Response r(client, num); - PrintError(r, std::current_exception()); - return CommandResult::ERROR; } diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx index 038b38309..6ae885621 100644 --- a/src/command/AllCommands.hxx +++ b/src/command/AllCommands.hxx @@ -25,12 +25,9 @@ class Client; void -command_init(); - -void -command_finish(); +command_init() noexcept; CommandResult -command_process(Client &client, unsigned num, char *line); +command_process(Client &client, unsigned num, char *line) noexcept; #endif diff --git a/src/decoder/Control.cxx b/src/decoder/Control.cxx index 4d106015d..fadbfd6a2 100644 --- a/src/decoder/Control.cxx +++ b/src/decoder/Control.cxx @@ -138,6 +138,18 @@ DecoderControl::Seek(std::unique_lock &lock, SongTime t) seek_error = false; SynchronousCommandLocked(lock, DecoderCommand::SEEK); + while (state == DecoderState::START) + /* If the decoder falls back to DecoderState::START, + this means that our SEEK command arrived too late, + and the decoder had meanwhile finished decoding and + went idle. Our SEEK command is finished, but that + means only that the decoder thread has launched the + decoder. To work around illegal states, we wait + until the decoder plugin has become ready. This is + a kludge, built on top of the "late seek" kludge. + Not exactly elegant, sorry. */ + WaitForDecoder(lock); + if (seek_error) throw std::runtime_error("Decoder failed to seek"); } diff --git a/src/decoder/Thread.cxx b/src/decoder/Thread.cxx index 747f2438a..d7cc10eaf 100644 --- a/src/decoder/Thread.cxx +++ b/src/decoder/Thread.cxx @@ -418,6 +418,11 @@ static void decoder_run_song(DecoderControl &dc, const DetachedSong &song, const char *uri, Path path_fs) { + if (dc.command == DecoderCommand::SEEK) + /* if the SEEK command arrived too late, start the + decoder at the seek position */ + dc.start_time = dc.seek_time; + DecoderBridge bridge(dc, dc.start_time.IsPositive(), /* pass the song tag only if it's authoritative, i.e. if it's a local diff --git a/src/output/Thread.cxx b/src/output/Thread.cxx index ffe57ed10..f9e521299 100644 --- a/src/output/Thread.cxx +++ b/src/output/Thread.cxx @@ -158,6 +158,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format, } catch (...) { LogError(std::current_exception()); Failure(std::current_exception()); + return; } if (f != in_audio_format || f != output->out_audio_format) @@ -457,7 +458,7 @@ AudioOutputControl::Task() noexcept case Command::RELEASE: if (!open) { /* the output has failed after - the PAUSE command was submitted; bail + the RELEASE command was submitted; bail out */ CommandFinished(); break;