release v0.22.5

-----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEA5IzWngIOJSkMBxDI26KWMbbRRIFAmAq1woQHG1heEBtdXNp
 Y3BkLm9yZwAKCRAjbopYxttFElB1EACItrIKgEywkzW3l+gmgSjtwwQOiLfg+0Zg
 Z3YgpegDvhmjBVXAHFDlhnXf//zCr286ZmCCVItrz2eGHYX2lvul0SdYxp/+Kebk
 WrCez6LMecaoGjbgiwQ70u/stNkX85ZT62CznNyYvwMx4bRhXXgOuBdKYTAZTvT8
 ABvfL+Ari7TBi88qCAaufmxyv7VFOaZg8+GpV1unIlHE6vu3febzDffPdjfODmOe
 BpLILJJIzUd9p1tGmSCvNCUUHdElktbK1aSVS/0x2xdKG3eDKmPIhSdxdqOdunr7
 9us4Mg7ZB5REaRC0ZfxR6P+vId0uIT3kpyDqs5i8Zao1WwmCdZhvaMMxJ3KF0MVs
 q4Lb99LMF2xAvsoA4x+wY0E0SlFrBhySrFY/i4gaBd9ctzQsbxID3cOZhSbEmQnk
 VNlPK/cYtWVHouLzSOUZeg3/nyMMWXTXy87esB/JdKWqushYLFqy/WIIJvKh4dRL
 YTEJtGeAe7wn9BPoD5Sf1xaj9ULw5CG/Z72inMk1rdzQBn+sWypb8HwJiGtHH43Q
 3YwTSAg/Z3MuxcMM1F9ce/IeE+sqCtOZKgTTpdp56hPlHMV9Fa0v7mnMHz508jB/
 4ZwAm3eEbCy14IKtW1jfKwA/IgPnF6bR6D1nn7F9SKnBG+hdHsyAVyHaTsXbfO4u
 0RZ5Y9vxdQ==
 =I2BI
 -----END PGP SIGNATURE-----

Merge tag 'v0.22.5'

release v0.22.5
This commit is contained in:
Max Kellermann 2021-02-15 22:50:16 +01:00
commit ecc07e4e98
17 changed files with 152 additions and 30 deletions

12
NEWS
View File

@ -2,13 +2,23 @@ ver 0.23 (not yet released)
* protocol
- new command "getvol"
ver 0.22.5 (not yet released)
ver 0.22.5 (2021/02/15)
* protocol
- error for malformed ranges instead of ignoring silently
- better error message for open-ended range with "move"
* database
- simple: fix missing CUE sheet metadata in "addid" command
* tags
- id: translate TPE3 to Conductor, not Performer
* archive
- iso9660: another fix for unaligned reads
* output
- httpd: error handling on Windows improved
- pulse: fix deadlock with "always_on"
* Windows:
- enable https:// support (via Schannel)
* Android
- work around "Permission denied" on mpd.conf
ver 0.22.4 (2021/01/21)
* protocol

View File

@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true"
android:requestLegacyExternalStorage="true"
android:icon="@drawable/icon"
android:banner="@drawable/icon"
android:label="@string/app_name">

View File

@ -5,8 +5,8 @@ android_ndk = get_option('android_ndk')
android_sdk = get_option('android_sdk')
android_abi = get_option('android_abi')
android_sdk_build_tools_version = '27.0.0'
android_sdk_platform = 'android-23'
android_sdk_build_tools_version = '29.0.3'
android_sdk_platform = 'android-29'
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)

View File

@ -68,11 +68,11 @@ There are two active branches in the git repository:
- the "unstable" branch called ``master`` where new features are
merged. This will become the next major release eventually.
- the "stable" branch (currently called ``v0.21.x``) where only bug
- the "stable" branch (currently called ``v0.22.x``) where only bug
fixes are merged.
Once :program:`MPD` 0.22 is released, a new branch called ``v0.22.x``
will be created for 0.22 bug-fix releases; after that, ``v0.21.x``
Once :program:`MPD` 0.23 is released, a new branch called ``v0.23.x``
will be created for 0.23 bug-fix releases; after that, ``v0.22.x``
will eventually cease to be maintained.
After bug fixes have been added to the "stable" branch, it will be

View File

@ -689,6 +689,11 @@ Whenever possible, ids should be used.
(directories add recursively). ``URI``
can also be a single file.
Clients that are connected via local socket may add arbitrary
local files (URI is an absolute path). Exmaple::
add "/home/foo/Music/bar.ogg"
.. _command_addid:
:command:`addid {URI} [POSITION]`

View File

@ -407,6 +407,9 @@ curl = AutotoolsProject(
'--disable-progress-meter',
'--disable-alt-svc',
'--without-gnutls', '--without-nss', '--without-libssh2',
# native Windows SSL/TLS support, option ignored on non-Windows builds
'--with-schannel',
],
patches='src/lib/curl/patches',

View File

@ -326,6 +326,11 @@ CommandResult
handle_move(Client &client, Request args, [[maybe_unused]] Response &r)
{
RangeArg range = args.ParseRange(0);
if (range.IsOpenEnded()) {
r.Error(ACK_ERROR_ARG, "Open-ended range not supported");
return CommandResult::ERROR;
}
int to = args.ParseInt(1);
client.GetPartition().MoveRange(range.start, range.end, to);
return CommandResult::OK;

View File

@ -37,6 +37,15 @@ public:
ExportedSong(const char *_uri, Tag &&_tag) noexcept
:LightSong(_uri, tag_buffer),
tag_buffer(std::move(_tag)) {}
/* this custom move constructor is necessary so LightSong::tag
points to this instance's #Tag field instead of leaving a
dangling reference to the source object's #Tag field */
ExportedSong(ExportedSong &&src) noexcept
:LightSong(src, tag_buffer),
tag_buffer(std::move(src.tag_buffer)) {}
ExportedSong &operator=(ExportedSong &&) = delete;
};
#endif

View File

@ -181,6 +181,14 @@ class AudioOutputControl {
*/
bool open = false;
/**
* Is the device currently playing, i.e. is its buffer
* (likely) non-empty? If not, then it will never be drained.
*
* This field is only valid while the output is open.
*/
bool playing;
/**
* Is the device paused? i.e. the output thread is in the
* ao_pause() loop.

View File

@ -53,7 +53,7 @@ AudioOutputControl::InternalOpen2(const AudioFormat in_audio_format)
if (open && cf != output->filter_audio_format)
/* if the filter's output format changes, the output
must be reopened as well */
InternalCloseOutput(true);
InternalCloseOutput(playing);
output->filter_audio_format = cf;
@ -64,6 +64,7 @@ AudioOutputControl::InternalOpen2(const AudioFormat in_audio_format)
}
open = true;
playing = false;
} else if (in_audio_format != output->out_audio_format) {
/* reconfigure the final ConvertFilter for its new
input AudioFormat */
@ -285,6 +286,9 @@ AudioOutputControl::PlayChunk(std::unique_lock<Mutex> &lock) noexcept
assert(nbytes % output->out_audio_format.GetFrameSize() == 0);
source.ConsumeData(nbytes);
/* there's data to be drained from now on */
playing = true;
}
return true;
@ -371,6 +375,9 @@ AudioOutputControl::InternalPause(std::unique_lock<Mutex> &lock) noexcept
}
skip_delay = true;
/* ignore drain commands until we got something new to play */
playing = false;
}
static void
@ -390,6 +397,10 @@ PlayFull(FilteredAudioOutput &output, ConstBuffer<void> _buffer)
inline void
AudioOutputControl::InternalDrain() noexcept
{
/* after this method finishes, there's nothing left to be
drained */
playing = false;
try {
/* flush the filter and play its remaining output */
@ -518,6 +529,7 @@ AudioOutputControl::Task() noexcept
source.Cancel();
if (open) {
playing = false;
const ScopeUnlock unlock(mutex);
output->Cancel();
}

View File

@ -56,8 +56,6 @@ class PulseOutput final : AudioOutput {
size_t writable;
bool pause;
/**
* Was Interrupt() called? This will unblock Play(). It will
* be reset by Cancel() and Pause(), as documented by the
@ -113,6 +111,7 @@ public:
[[nodiscard]] std::chrono::steady_clock::duration Delay() const noexcept override;
size_t Play(const void *chunk, size_t size) override;
void Drain() override;
void Cancel() noexcept override;
bool Pause() override;
@ -688,7 +687,6 @@ PulseOutput::Open(AudioFormat &audio_format)
"pa_stream_connect_playback() has failed");
}
pause = false;
interrupted = false;
}
@ -699,17 +697,6 @@ PulseOutput::Close() noexcept
Pulse::LockGuard lock(mainloop);
if (pa_stream_get_state(stream) == PA_STREAM_READY) {
pa_operation *o =
pa_stream_drain(stream,
pulse_output_stream_success_cb, this);
if (o == nullptr) {
LogPulseError(context,
"pa_stream_drain() has failed");
} else
pulse_wait_for_operation(mainloop, o);
}
DeleteStream();
if (context != nullptr &&
@ -780,7 +767,7 @@ PulseOutput::Delay() const noexcept
Pulse::LockGuard lock(mainloop);
auto result = std::chrono::steady_clock::duration::zero();
if (pause && pa_stream_is_corked(stream) &&
if (pa_stream_is_corked(stream) &&
pa_stream_get_state(stream) == PA_STREAM_READY)
/* idle while paused */
result = std::chrono::seconds(1);
@ -796,8 +783,6 @@ PulseOutput::Play(const void *chunk, size_t size)
Pulse::LockGuard lock(mainloop);
pause = false;
/* check if the stream is (already) connected */
WaitStream();
@ -840,6 +825,25 @@ PulseOutput::Play(const void *chunk, size_t size)
return size;
}
void
PulseOutput::Drain()
{
Pulse::LockGuard lock(mainloop);
if (pa_stream_get_state(stream) != PA_STREAM_READY ||
pa_stream_is_suspended(stream) ||
pa_stream_is_corked(stream))
return;
pa_operation *o =
pa_stream_drain(stream,
pulse_output_stream_success_cb, this);
if (o == nullptr)
throw MakePulseError(context, "pa_stream_drain() failed");
pulse_wait_for_operation(mainloop, o);
}
void
PulseOutput::Cancel() noexcept
{
@ -876,7 +880,6 @@ PulseOutput::Pause()
Pulse::LockGuard lock(mainloop);
pause = true;
interrupted = false;
/* check if the stream is (already/still) connected */

View File

@ -24,6 +24,7 @@
#include "mixer/MixerList.hxx"
#include "thread/Cond.hxx"
#include "thread/Mutex.hxx"
#include "thread/Name.hxx"
#include "thread/Thread.hxx"
#include "util/AllocatedString.hxx"
#include "util/Domain.hxx"
@ -231,6 +232,7 @@ IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept {
}
void WasapiOutputThread::Work() noexcept {
SetThreadName("Wasapi Output Worker");
FormatDebug(wasapi_output_domain, "Working thread started");
try {
com.emplace();

View File

@ -94,7 +94,7 @@ ParseCommandArgRange(const char *s)
s);
if (test == test2)
value = std::numeric_limits<int>::max();
return RangeArg::OpenEnded(range.start);
if (value < 0)
throw FormatProtocolError(ACK_ERROR_ARG,
@ -107,9 +107,13 @@ ParseCommandArgRange(const char *s)
range.end = (unsigned)value;
} else {
range.end = (unsigned)value + 1;
return RangeArg::Single(range.start);
}
if (!range.IsWellFormed())
throw FormatProtocolError(ACK_ERROR_ARG,
"Malformed range: %s", s);
return range;
}

View File

@ -25,8 +25,22 @@
struct RangeArg {
unsigned start, end;
static constexpr RangeArg All() {
return { 0, std::numeric_limits<unsigned>::max() };
/**
* Construct an open-ended range starting at the given index.
*/
static constexpr RangeArg OpenEnded(unsigned start) noexcept {
return { start, std::numeric_limits<unsigned>::max() };
}
static constexpr RangeArg All() noexcept {
return OpenEnded(0);
}
/**
* Construct an instance describing exactly one index.
*/
static constexpr RangeArg Single(unsigned i) noexcept {
return { i, i + 1 };
}
constexpr bool operator==(RangeArg other) const noexcept {
@ -37,13 +51,45 @@ struct RangeArg {
return !(*this == other);
}
constexpr bool IsOpenEnded() const noexcept {
return end == All().end;
}
constexpr bool IsAll() const noexcept {
return *this == All();
}
constexpr bool IsWellFormed() const noexcept {
return start <= end;
}
/**
* Is this range empty? A malformed range also counts as
* "empty" for this method.
*/
constexpr bool IsEmpty() const noexcept {
return start >= end;
}
/**
* Check if the range contains at least this number of items.
* Unlike Count(), this allows the object to be malformed.
*/
constexpr bool HasAtLeast(unsigned n) const noexcept {
return start + n <= end;
}
constexpr bool Contains(unsigned i) const noexcept {
return i >= start && i < end;
}
/**
* Count the number of items covered by this range. This requires the
* object to be well-formed.
*/
constexpr unsigned Count() const noexcept {
return end - start;
}
};
#endif

View File

@ -88,6 +88,19 @@ struct LightSong {
LightSong(const char *_uri, const Tag &_tag) noexcept
:uri(_uri), tag(_tag) {}
/**
* A copy constructor which copies all fields, but only sets
* the tag to a caller-provided reference. This is used by
* the #ExportedSong move constructor.
*/
LightSong(const LightSong &src, const Tag &_tag) noexcept
:directory(src.directory), uri(src.uri),
real_uri(src.real_uri),
tag(_tag),
mtime(src.mtime),
start_time(src.start_time), end_time(src.end_time),
audio_format(src.audio_format) {}
gcc_pure
std::string GetURI() const noexcept {
if (directory == nullptr)

View File

@ -112,6 +112,6 @@ template <typename T>
void swap(ComPtr<T> &lhs, ComPtr<T> &rhs) noexcept {
lhs.swap(rhs);
}
}
} // namespace std
#endif

View File

@ -59,6 +59,7 @@ case x:
C(E_INVALIDARG);
C(E_OUTOFMEMORY);
C(E_POINTER);
C(NO_ERROR);
#undef C
}
return std::string_view();