Compare commits

...

46 Commits

Author SHA1 Message Date
Max Kellermann
323231d1dd release v0.20.12 2017-11-25 19:32:37 +01:00
Max Kellermann
714011c81e lib/upnp: adapt to libupnp 1.8 API changes
Closes 
2017-11-16 11:39:11 +01:00
Max Kellermann
952ff4207b lib/upnp/Callback: make "evp" parameter const 2017-11-16 11:37:58 +01:00
Max Kellermann
150b16ec2c lib/upnp/Discovery: make Upnp_Discovery pointers const 2017-11-16 11:37:04 +01:00
Max Kellermann
c98bc4a243 playlist/PlaylistRegistry: use LockRewind() instead of Rewind()
Fixes a deadlock caused by commit
31ab78ae8e.  That commit was not
actually bad - just these two calls have always been bad, which went
unnoticed for a long time.
2017-11-14 21:19:22 +01:00
Max Kellermann
014f8cd693 output/httpd: flush encoder after tag
Without the flush, ReadPage() may not return any data, or not all
data.  This may result in incomplete ddata the new "header" page,
corrupting streams with some encoders such as Vorbis.

Fixes 
2017-11-14 12:00:14 +01:00
Max Kellermann
aea37e46e3 encoder/vorbis: default to quality 3
Don't require a quality or bitrate setting.  If nothing is set, don't
fail startup - just go with a good default.  A quality setting of 3 is
what "oggenc" defaults to as well.
2017-11-14 11:30:28 +01:00
Max Kellermann
31ab78ae8e input/{cdio,ffmpeg,file,smbclient}: unlock the mutex during blocking I/O
InputStream::Read() and InputStream::Seek() are called with the mutex
locked.  That means the implementation must not block, or unlock the
mutex before calling into blocking code.

Previously, a slow CD drive could stall the whole MPD process,
including the main thread, due to this problem.

Closes 
2017-11-13 17:13:10 +01:00
Max Kellermann
f82e1453e4 input/smbclient: use std::lock_guard 2017-11-13 17:13:10 +01:00
Max Kellermann
a2b77c8813 decoder/ffmpeg, test/test_protocol: catch exceptions by reference
Work around -Werror=catch-value.
2017-11-12 18:54:29 +01:00
Max Kellermann
18add29472 configure.ac: disable -Wnoexcept-type
Workaround for .
2017-11-12 18:54:29 +01:00
cathugger
b111a8fe8d output/Thread: ensure pending tags are flushed in all cases
Fixes hanging playback with soxr resampler.

Closes , 
2017-11-05 17:42:32 +01:00
Marcin Jurkowski
3b23cf0258 decoder/vorbis: scale and clip tremor-decoded samples to 15 bits
Tremor decoder is unusable since commit 2ee43c4. Sound is distorted to
the point where it's nothing but noise.

The data from vorbis_synthesis_pcmout() needs to be scaled and
clipped for 16 bit sample size. For reference see
http://lists.xiph.org/pipermail/tremor/2010-April/001642.html and
http://lists.xiph.org/pipermail/vorbis/2006-October/026513.html.

Signed-off-by: Marcin Jurkowski <marcin1j@gmail.com>
2017-11-03 19:45:41 +01:00
Max Kellermann
28e864e096 player/Thread: log message when decoder is too slow 2017-10-25 20:26:09 +02:00
Max Kellermann
1de19b921a input/curl: call StartRequest() after setting CURLOPT_RANGE
It's not possible to set CURL options after curl_easy_perform(), and
thus the CURLOPT_RANGE had no effect.
2017-10-24 21:43:39 +02:00
Max Kellermann
ff162b5a03 input/curl: move code to StartRequest() 2017-10-24 21:41:17 +02:00
Max Kellermann
d8e4705dd4 input/curl: move the range buffer to the stack
From the CURLOPT_RANGE documentation: "The application does not have
to keep the string around after setting this option."
2017-10-24 21:38:35 +02:00
Max Kellermann
338e1f5926 increment version number to 0.20.12 2017-10-24 17:31:55 +02:00
Max Kellermann
a7fdfa08e1 release v0.20.11 2017-10-18 10:14:46 +02:00
Max Kellermann
9703a401c5 Playlist{File,Save}: always use UTF-8 in playlists on Windows
Turns out that using CP_ACP is a lousy idea, because only very few
Unicode characters can be represented by it.  Instead, switch to UTF-8
(which every sane person on other operating system already uses).

Closes 
2017-10-18 10:05:26 +02:00
Max Kellermann
753a2aa462 PlaylistSave: move code to playlist_print_path() 2017-10-18 09:51:04 +02:00
Max Kellermann
10990a0684 queue/Playlist: call MoveOrderToCurrent() in SeekSongOrder() on song change
Applies the improvements from the previous commit to the "seek"
commands, which are also capable of switching songs.

Closes 
2017-10-18 09:14:27 +02:00
Max Kellermann
91254e9211 queue/PlaylistControl: keep order list consistency in MoveOrderToCurrent()
Our previous use of Queue::SwapOrders() could cause surprising
results:

- sometimes, the old "current" song would be played again (if the
  newly selected song had not been played already)

- sometimes, the old "current" song would not be played again (if the
  newly selected song had already been played)

This is inconsistent, because it should not depend on whether the
newly selected song had already been played.

So instead of Queue::SwapOrders() we now use Queue::MoveOrderAfter()
and Queue::MoveOrderBefore(), which is more expensive, but also more
consistent.  It attempts to retain as much from the previous order
list as possible, and only moves the newly selected song around.
2017-10-18 09:05:47 +02:00
Max Kellermann
0f79287b04 queue/Playlist: move code to MoveOrderToCurrent() 2017-10-18 09:05:24 +02:00
Max Kellermann
f2fac77d8c queue/Queue: add methods MoveOrderBefore() and MoveOrderAfter() 2017-10-18 08:50:01 +02:00
Max Kellermann
81b7373637 queue/Queue: MoveOrder() returns to_order 2017-10-18 08:46:31 +02:00
Max Kellermann
fa67c2548a decoder/Thread: clear the command after catching an exception
If an early exception gets caught (e.g. from
AllocatedPath::FromUTF8Throw()) before
DecoderControl::CommandFinishedLocked() is called, the decoder thread
would go in an endless loop, because DecoderCommand::START is still
set.

Closes 
2017-09-27 17:08:16 +02:00
John Regan
ea80587ddb GME Plugin: fix track numbering
GME starts all track indexes at zero, but subtune prefixes
start at one. This fixes an off-by-one error during track
enumeration.
2017-09-27 11:18:03 +02:00
Max Kellermann
828f5f8384 lib/icu/CaseFold: disable broken strxfrm() callback 2017-09-20 23:55:14 +02:00
Max Kellermann
1295a1272a lib/icu/Compare: add fallback using strcasecmp() and strcasestr()
Our IcuCaseFold() fallback using strxfrm() is not actually case
insensitive.  This commit fixes the problem by switching to
strcasecmp().  That function is not guaranteed to support UTF-8, but
it's the best we can do in this sparse situation.

Closes 
2017-09-20 23:43:27 +02:00
Max Kellermann
66646d9276 SongFilter: use class IcuCompare 2017-09-20 23:43:26 +02:00
Max Kellermann
d0497dba92 lib/icu/Compare: OO wrapper for IcuCaseFold() 2017-09-20 23:32:55 +02:00
Max Kellermann
42914e8227 lib/icu/CaseFold: add "noexcept" 2017-09-20 23:32:54 +02:00
Max Kellermann
59b49b7881 db/Selection: add missing config.h 2017-09-20 23:32:54 +02:00
Max Kellermann
5620f16330 lib/icu/Collate: move IcuCaseFold() to CaseFold.cxx 2017-09-20 23:11:58 +02:00
Max Kellermann
be024d4ad7 lib/icu/Collate: remove unnecessary assert() 2017-09-20 23:05:31 +02:00
Max Kellermann
75c740fe2b output/sndio: fix indent 2017-09-19 18:50:35 +02:00
Max Kellermann
6c8d86bb90 output/sndio: rename the "sio_hdl" variable to avoid clash with struct name 2017-09-19 18:49:33 +02:00
Charlie Waters
b253a6b71e ffmpeg plugin: when decoded stream duration is unavailable, attempt fallback to container duration (fix ) 2017-09-18 10:39:27 +02:00
Max Kellermann
ca7b4df812 doc/user: document the Opus encoder 2017-09-07 14:21:40 +02:00
Max Kellermann
bc8dd57236 doc/protocol.xml: document status/volume=-1
Closes 
2017-09-04 08:15:41 +02:00
Max Kellermann
f4f461b8bb storage/curl: support Content-Type application/xml 2017-09-01 11:32:40 +02:00
Max Kellermann
cbb9b6957f storage/curl: use StringStartsWith() 2017-09-01 11:31:10 +02:00
Max Kellermann
f6b56c9317 storage/curl: move code to IsXmlContentType() 2017-09-01 11:30:30 +02:00
Max Kellermann
3717fb6c8d win32/build.py: add -march=pentium3 to fix 32 bit LAME build
Workaround for the following LAME build failure:

 error: inlining failed in call to always_inline '_mm_sqrt_ps': target
 specific option mismatch

This is because the LAME build scripts do not check whether SSE is
available; they only check for the presence of the "xmmintrin.h"
header.

Requiring a Pentium 3 CPU is reasonable enough, and it's the first CPU
to feature SSE support.
2017-08-31 19:48:59 +02:00
Max Kellermann
f6abbc01bd increment version number to 0.20.11 2017-08-31 19:48:59 +02:00
44 changed files with 726 additions and 188 deletions

@@ -243,6 +243,7 @@ CURL_SOURCES = \
src/lib/curl/Slist.hxx
UPNP_SOURCES = \
src/lib/upnp/Compat.hxx \
src/lib/upnp/Init.cxx src/lib/upnp/Init.hxx \
src/lib/upnp/ClientInit.cxx src/lib/upnp/ClientInit.hxx \
src/lib/upnp/Device.cxx src/lib/upnp/Device.hxx \
@@ -518,6 +519,8 @@ libevent_a_SOURCES = \
# UTF-8 library
libicu_a_SOURCES = \
src/lib/icu/CaseFold.cxx src/lib/icu/CaseFold.hxx \
src/lib/icu/Compare.cxx src/lib/icu/Compare.hxx \
src/lib/icu/Collate.cxx src/lib/icu/Collate.hxx \
src/lib/icu/Converter.cxx src/lib/icu/Converter.hxx

29
NEWS

@@ -1,3 +1,32 @@
ver 0.20.12 (2017/11/25)
* database
- upnp: adapt to libupnp 1.8 API changes
* input
- cdio_paranoia, ffmpeg, file, smbclient: reduce lock contention,
fixing lots of xrun problems
- curl: fix seeking
* decoder
- ffmpeg: fix GCC 8 warning
- vorbis: fix Tremor support
* player
- log message when decoder is too slow
* encoder
- vorbis: default to quality 3
* output
- fix hanging playback with soxr resampler
- httpd: flush encoder after tag; fixes corrupt Vorbis stream
ver 0.20.11 (2017/10/18)
* storage
- curl: support Content-Type application/xml
* decoder
- ffmpeg: more reliable song duration
- gme: fix track numbering
* improve random song order when switching songs manually
* fix case insensitive search without libicu
* fix Unicode file names in playlists on Windows
* fix endless loop when accessing malformed file names in ZIP files
ver 0.20.10 (2017/08/24)
* decoder
- ffmpeg: support MusicBrainz ID3v2 tags

@@ -1,10 +1,10 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.20.10, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.20.12, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=20
VERSION_REVISION=10
VERSION_REVISION=12
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
@@ -241,6 +241,7 @@ AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
AC_CHECK_FUNCS(initgroups)
AC_CHECK_FUNCS(fnmatch)
AC_CHECK_FUNCS(strndup)
AC_CHECK_FUNCS(strcasestr)
if test x$host_is_linux = xyes; then
MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
@@ -1384,6 +1385,11 @@ then
AX_APPEND_COMPILE_FLAGS([-Wcast-qual])
AX_APPEND_COMPILE_FLAGS([-Wwrite-strings])
AX_APPEND_COMPILE_FLAGS([-Wsign-compare])
dnl This GCC8 warning for C++17 ABI compatibility is of no
dnl interest for us, because we're not a shared library.
AX_APPEND_COMPILE_FLAGS([-Wno-noexcept-type])
AC_LANG_POP
fi

@@ -445,7 +445,9 @@
<listitem>
<para>
<varname>volume</varname>:
<returnvalue>0-100</returnvalue>
<returnvalue>0-100</returnvalue> or
<returnvalue>-1</returnvalue> if the volume cannot
be determined
</para>
</listitem>
<listitem>

@@ -2991,6 +2991,60 @@ run</programlisting>
</informaltable>
</section>
<section id="opus_encoder">
<title><varname>opus</varname></title>
<para>
Encodes into <ulink
url="http://www.opus-codec.org/">Ogg Opus</ulink>.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>bitrate</varname>
</entry>
<entry>
Sets the data rate in bit per second. The special
value "auto" lets <application>libopus</application>
choose a rate (which is the default), and "max" uses
the maximum possible data rate.
</entry>
</row>
<row>
<entry>
<varname>complexity</varname>
</entry>
<entry>
Sets the <ulink
url="https://wiki.xiph.org/OpusFAQ#What_is_the_complexity_of_Opus.3F">Opus
complexity</ulink>.
</entry>
</row>
<row>
<entry>
<varname>signal</varname>
</entry>
<entry>
Sets the Opus signal type. Valid values are "auto"
(the default), "voice" and "music".
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section id="vorbis_encoder">
<title><varname>vorbis</varname></title>
@@ -3014,8 +3068,8 @@ run</programlisting>
</entry>
<entry>
Sets the quality for VBR. -1 is the lowest quality,
10 is the highest quality. Cannot be used with
<varname>bitrate</varname>.
10 is the highest quality. Defaults to 3. Cannot
be used with <varname>bitrate</varname>.
</entry>
</row>
<row>

@@ -207,13 +207,12 @@ try {
continue;
#ifdef _UNICODE
wchar_t buffer[MAX_PATH];
auto result = MultiByteToWideChar(CP_ACP, 0, s, -1,
buffer, ARRAY_SIZE(buffer));
if (result <= 0)
/* on Windows, playlists always contain UTF-8, because
its "narrow" charset (i.e. CP_ACP) is incapable of
storing all Unicode paths */
const auto path = AllocatedPath::FromUTF8(s);
if (path.IsNull())
continue;
const Path path = Path::FromFS(buffer);
#else
const Path path = Path::FromFS(s);
#endif

@@ -28,13 +28,25 @@
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
#include "fs/NarrowPath.hxx"
#include "fs/io/FileOutputStream.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "util/UriUtil.hxx"
#include <stdexcept>
static void
playlist_print_path(BufferedOutputStream &os, const Path path)
{
#ifdef _UNICODE
/* on Windows, playlists always contain UTF-8, because its
"narrow" charset (i.e. CP_ACP) is incapable of storing all
Unicode paths */
os.Format("%s\n", path.ToUTF8().c_str());
#else
os.Format("%s\n", path.c_str());
#endif
}
void
playlist_print_song(BufferedOutputStream &os, const DetachedSong &song)
{
@@ -44,7 +56,7 @@ playlist_print_song(BufferedOutputStream &os, const DetachedSong &song)
try {
const auto uri_fs = AllocatedPath::FromUTF8Throw(uri_utf8);
os.Format("%s\n", NarrowPath(uri_fs).c_str());
playlist_print_path(os, uri_fs);
} catch (const std::runtime_error &) {
}
}
@@ -63,7 +75,7 @@ playlist_print_uri(BufferedOutputStream &os, const char *uri)
AllocatedPath::FromUTF8Throw(uri);
if (!path.IsNull())
os.Format("%s\n", NarrowPath(path).c_str());
playlist_print_path(os, path);
} catch (const std::runtime_error &) {
}
}

@@ -27,7 +27,7 @@
#include "util/ASCII.hxx"
#include "util/TimeParser.hxx"
#include "util/UriUtil.hxx"
#include "lib/icu/Collate.hxx"
#include "lib/icu/CaseFold.hxx"
#include <stdexcept>
@@ -57,17 +57,10 @@ locate_parse_type(const char *str) noexcept
return tag_name_parse_i(str);
}
static AllocatedString<>
ImportString(const char *p, bool fold_case)
{
return fold_case
? IcuCaseFold(p)
: AllocatedString<>::Duplicate(p);
}
SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
:tag(_tag), fold_case(_fold_case),
value(ImportString(_value, _fold_case))
:tag(_tag),
value(AllocatedString<>::Duplicate(_value)),
fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare())
{
}
@@ -87,9 +80,7 @@ SongFilter::Item::StringMatch(const char *s) const noexcept
assert(tag != LOCATE_TAG_MODIFIED_SINCE);
if (fold_case) {
const auto folded = IcuCaseFold(s);
assert(!folded.IsNull());
return StringFind(folded.c_str(), value.c_str()) != nullptr;
return fold_case.IsIn(s);
} else {
return StringIsEqual(s, value.c_str());
}

@@ -20,6 +20,7 @@
#ifndef MPD_SONG_FILTER_HXX
#define MPD_SONG_FILTER_HXX
#include "lib/icu/Compare.hxx"
#include "util/AllocatedString.hxx"
#include "Compiler.h"
@@ -48,10 +49,13 @@ public:
class Item {
uint8_t tag;
bool fold_case;
AllocatedString<> value;
/**
* This value is only set if case folding is enabled.
*/
IcuCompare fold_case;
/**
* For #LOCATE_TAG_MODIFIED_SINCE
*/

@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Selection.hxx"
#include "SongFilter.hxx"

@@ -508,6 +508,7 @@ try {
decoder_run_song(dc, song, uri_utf8, path_fs);
} catch (...) {
dc.state = DecoderState::ERROR;
dc.command = DecoderCommand::NONE;
dc.error = std::current_exception();
dc.client_cond.signal();
}

@@ -258,7 +258,7 @@ FfmpegSendFrame(DecoderClient &client, InputStream &is,
try {
output_buffer = copy_interleave_frame(codec_context, frame,
buffer);
} catch (const std::exception e) {
} catch (const std::exception &e) {
/* this must be a serious error, e.g. OOM */
LogError(e);
return DecoderCommand::STOP;
@@ -712,7 +712,9 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
#endif
const SignedSongTime total_time =
FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base);
av_stream.duration != (int64_t)AV_NOPTS_VALUE
? FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base)
: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
client.Ready(audio_format, input.IsSeekable(), total_time);
@@ -842,6 +844,10 @@ FfmpegScanStream(AVFormatContext &format_context,
tag_handler_invoke_duration(handler, handler_ctx,
FromFfmpegTime(stream.duration,
stream.time_base));
else if (format_context.duration != (int64_t)AV_NOPTS_VALUE)
tag_handler_invoke_duration(handler, handler_ctx,
FromFfmpegTime(format_context.duration,
AV_TIME_BASE_Q));
FfmpegScanMetadata(format_context, audio_stream, handler, handler_ctx);

@@ -293,13 +293,13 @@ gme_container_scan(Path path_fs)
TagBuilder tag_builder;
auto tail = list.before_begin();
for (unsigned i = 1; i <= num_songs; ++i) {
for (unsigned i = 0; i < num_songs; ++i) {
ScanMusicEmu(emu, i,
add_tag_handler, &tag_builder);
char track_name[64];
snprintf(track_name, sizeof(track_name),
SUBTUNE_PREFIX "%03u.%s", i, subtune_suffix);
SUBTUNE_PREFIX "%03u.%s", i+1, subtune_suffix);
tail = list.emplace_after(tail, track_name,
tag_builder.Commit());
}

@@ -178,6 +178,20 @@ VorbisDecoder::SubmitInit()
client.Ready(audio_format, eos_granulepos > 0, duration);
}
#ifdef HAVE_TREMOR
static inline int16_t tremor_clip_sample(int32_t x)
{
x >>= 9;
if (x < INT16_MIN)
return INT16_MIN;
if (x > INT16_MAX)
return INT16_MAX;
return x;
}
#endif
bool
VorbisDecoder::SubmitSomePcm()
{
@@ -197,7 +211,7 @@ VorbisDecoder::SubmitSomePcm()
auto *dest = &buffer[c];
for (size_t i = 0; i < n_frames; ++i) {
*dest = *src++;
*dest = tremor_clip_sample(*src++);
dest += channels;
}
}

@@ -62,7 +62,7 @@ private:
};
class PreparedVorbisEncoder final : public PreparedEncoder {
float quality;
float quality = 3;
int bitrate;
public:
@@ -97,7 +97,7 @@ PreparedVorbisEncoder::PreparedVorbisEncoder(const ConfigBlock &block)
value = block.GetBlockValue("bitrate");
if (value == nullptr)
throw std::runtime_error("neither bitrate nor quality defined");
return;
quality = -2.0;

@@ -270,7 +270,10 @@ CdioParanoiaInputStream::Seek(offset_type new_offset)
lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
offset = new_offset;
cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET);
{
const ScopeUnlock unlock(mutex);
cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET);
}
}
size_t
@@ -292,6 +295,8 @@ CdioParanoiaInputStream::Read(void *ptr, size_t length)
//current sector was changed ?
if (lsn_relofs != buffer_lsn) {
const ScopeUnlock unlock(mutex);
rbuf = cdio_paranoia_read(para, nullptr);
s_err = cdda_errors(drv);

@@ -64,7 +64,6 @@ static const size_t CURL_RESUME_AT = 384 * 1024;
struct CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
/* some buffers which were passed to libcurl, which we have
too free */
char range[32];
CurlSlist request_headers;
CurlRequest *request = nullptr;
@@ -86,8 +85,19 @@ struct CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
static InputStream *Open(const char *url, Mutex &mutex, Cond &cond);
/**
* Create and initialize a new #CurlRequest instance. After
* this, you may add more request headers and set options. To
* actually start the request, call StartRequest().
*/
void InitEasy();
/**
* Start the request after having called InitEasy(). After
* this, you must not set any CURL options.
*/
void StartRequest();
/**
* Frees the current "libcurl easy" handle, and everything
* associated with it.
@@ -372,6 +382,11 @@ CurlInputStream::InitEasy()
request_headers.Clear();
request_headers.Append("Icy-Metadata: 1");
}
void
CurlInputStream::StartRequest()
{
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
request->Start();
@@ -398,6 +413,7 @@ CurlInputStream::SeekInternal(offset_type new_offset)
/* send the "Range" header */
if (offset > 0) {
char range[32];
#ifdef WIN32
// TODO: what can we use on Windows to format 64 bit?
sprintf(range, "%lu-", (long)offset);
@@ -406,6 +422,8 @@ CurlInputStream::SeekInternal(offset_type new_offset)
#endif
request->SetOption(CURLOPT_RANGE, range);
}
StartRequest();
}
void
@@ -428,6 +446,7 @@ CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond)
try {
BlockingCall(io_thread_get(), [c](){
c->InitEasy();
c->StartRequest();
});
} catch (...) {
delete c;

@@ -104,7 +104,13 @@ input_ffmpeg_open(const char *uri,
size_t
FfmpegInputStream::Read(void *ptr, size_t read_size)
{
auto result = avio_read(h, (unsigned char *)ptr, read_size);
int result;
{
const ScopeUnlock unlock(mutex);
result = avio_read(h, (unsigned char *)ptr, read_size);
}
if (result <= 0) {
if (result < 0)
throw MakeFfmpegError(result, "avio_read() failed");
@@ -126,7 +132,12 @@ FfmpegInputStream::IsEOF() noexcept
void
FfmpegInputStream::Seek(offset_type new_offset)
{
auto result = avio_seek(h, new_offset, SEEK_SET);
int64_t result;
{
const ScopeUnlock unlock(mutex);
result = avio_seek(h, new_offset, SEEK_SET);
}
if (result < 0)
throw MakeFfmpegError(result, "avio_seek() failed");

@@ -87,14 +87,24 @@ input_file_open(gcc_unused const char *filename,
void
FileInputStream::Seek(offset_type new_offset)
{
reader.Seek((off_t)new_offset);
{
const ScopeUnlock unlock(mutex);
reader.Seek((off_t)new_offset);
}
offset = new_offset;
}
size_t
FileInputStream::Read(void *ptr, size_t read_size)
{
size_t nbytes = reader.Read(ptr, read_size);
size_t nbytes;
{
const ScopeUnlock unlock(mutex);
nbytes = reader.Read(ptr, read_size);
}
offset += nbytes;
return nbytes;
}

@@ -125,9 +125,14 @@ input_smbclient_open(const char *uri,
size_t
SmbclientInputStream::Read(void *ptr, size_t read_size)
{
smbclient_mutex.lock();
ssize_t nbytes = smbc_read(fd, ptr, read_size);
smbclient_mutex.unlock();
ssize_t nbytes;
{
const ScopeUnlock unlock(mutex);
const std::lock_guard<Mutex> lock(smbclient_mutex);
nbytes = smbc_read(fd, ptr, read_size);
}
if (nbytes < 0)
throw MakeErrno("smbc_read() failed");
@@ -138,9 +143,14 @@ SmbclientInputStream::Read(void *ptr, size_t read_size)
void
SmbclientInputStream::Seek(offset_type new_offset)
{
smbclient_mutex.lock();
off_t result = smbc_lseek(fd, new_offset, SEEK_SET);
smbclient_mutex.unlock();
off_t result;
{
const ScopeUnlock unlock(mutex);
const std::lock_guard<Mutex> lock(smbclient_mutex);
result = smbc_lseek(fd, new_offset, SEEK_SET);
}
if (result < 0)
throw MakeErrno("smbc_lseek() failed");

102
src/lib/icu/CaseFold.cxx Normal file

@@ -0,0 +1,102 @@
/*
* Copyright 2003-2017 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 "config.h"
#include "CaseFold.hxx"
#ifdef HAVE_ICU_CASE_FOLD
#include "util/AllocatedString.hxx"
#ifdef HAVE_ICU
#include "Util.hxx"
#include "util/AllocatedArray.hxx"
#include "util/ConstBuffer.hxx"
#include <unicode/ucol.h>
#include <unicode/ustring.h>
#else
#include <algorithm>
#include <ctype.h>
#endif
#ifdef WIN32
#include "Win32.hxx"
#include <windows.h>
#endif
#include <memory>
#include <stdexcept>
#include <assert.h>
#include <string.h>
AllocatedString<>
IcuCaseFold(const char *src) noexcept
try {
#ifdef HAVE_ICU
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(src != nullptr);
#endif
const auto u = UCharFromUTF8(src);
if (u.IsNull())
return AllocatedString<>::Duplicate(src);
AllocatedArray<UChar> folded(u.size() * 2u);
UErrorCode error_code = U_ZERO_ERROR;
size_t folded_length = u_strFoldCase(folded.begin(), folded.size(),
u.begin(), u.size(),
U_FOLD_CASE_DEFAULT,
&error_code);
if (folded_length == 0 || error_code != U_ZERO_ERROR)
return AllocatedString<>::Duplicate(src);
folded.SetSize(folded_length);
return UCharToUTF8({folded.begin(), folded.size()});
#elif defined(WIN32)
const auto u = MultiByteToWideChar(CP_UTF8, src);
const int size = LCMapStringEx(LOCALE_NAME_INVARIANT,
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
u.c_str(), -1, nullptr, 0,
nullptr, nullptr, 0);
if (size <= 0)
return AllocatedString<>::Duplicate(src);
std::unique_ptr<wchar_t[]> buffer(new wchar_t[size]);
if (LCMapStringEx(LOCALE_NAME_INVARIANT,
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
u.c_str(), -1, buffer.get(), size,
nullptr, nullptr, 0) <= 0)
return AllocatedString<>::Duplicate(src);
return WideCharToMultiByte(CP_UTF8, buffer.get());
#else
#error not implemented
#endif
} catch (const std::runtime_error &) {
return AllocatedString<>::Duplicate(src);
}
#endif /* HAVE_ICU_CASE_FOLD */

38
src/lib/icu/CaseFold.hxx Normal file

@@ -0,0 +1,38 @@
/*
* Copyright 2003-2017 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_ICU_CASE_FOLD_HXX
#define MPD_ICU_CASE_FOLD_HXX
#include "check.h"
#if defined(HAVE_ICU) || defined(_WIN32)
#define HAVE_ICU_CASE_FOLD
#include "Compiler.h"
template<typename T> class AllocatedString;
gcc_nonnull_all
AllocatedString<char>
IcuCaseFold(const char *src) noexcept;
#endif
#endif

@@ -23,8 +23,6 @@
#ifdef HAVE_ICU
#include "Util.hxx"
#include "util/AllocatedArray.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RuntimeError.hxx"
#include <unicode/ucol.h>
@@ -141,70 +139,3 @@ IcuCollate(const char *a, const char *b) noexcept
return strcoll(a, b);
#endif
}
AllocatedString<>
IcuCaseFold(const char *src)
try {
#ifdef HAVE_ICU
assert(collator != nullptr);
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(src != nullptr);
#endif
const auto u = UCharFromUTF8(src);
if (u.IsNull())
return AllocatedString<>::Duplicate(src);
AllocatedArray<UChar> folded(u.size() * 2u);
UErrorCode error_code = U_ZERO_ERROR;
size_t folded_length = u_strFoldCase(folded.begin(), folded.size(),
u.begin(), u.size(),
U_FOLD_CASE_DEFAULT,
&error_code);
if (folded_length == 0 || error_code != U_ZERO_ERROR)
return AllocatedString<>::Duplicate(src);
folded.SetSize(folded_length);
return UCharToUTF8({folded.begin(), folded.size()});
#elif defined(WIN32)
const auto u = MultiByteToWideChar(CP_UTF8, src);
const int size = LCMapStringEx(LOCALE_NAME_INVARIANT,
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
u.c_str(), -1, nullptr, 0,
nullptr, nullptr, 0);
if (size <= 0)
return AllocatedString<>::Duplicate(src);
std::unique_ptr<wchar_t[]> buffer(new wchar_t[size]);
if (LCMapStringEx(LOCALE_NAME_INVARIANT,
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
u.c_str(), -1, buffer.get(), size,
nullptr, nullptr, 0) <= 0)
return AllocatedString<>::Duplicate(src);
return WideCharToMultiByte(CP_UTF8, buffer.get());
#else
size_t size = strlen(src) + 1;
std::unique_ptr<char[]> buffer(new char[size]);
size_t nbytes = strxfrm(buffer.get(), src, size);
if (nbytes >= size) {
/* buffer too small - reallocate and try again */
buffer.reset();
size = nbytes + 1;
buffer.reset(new char[size]);
nbytes = strxfrm(buffer.get(), src, size);
}
assert(nbytes < size);
assert(buffer[nbytes] == 0);
return AllocatedString<>::Donate(buffer.release());
#endif
} catch (const std::runtime_error &) {
return AllocatedString<>::Duplicate(src);
}

@@ -23,8 +23,6 @@
#include "check.h"
#include "Compiler.h"
template<typename T> class AllocatedString;
/**
* Throws #std::runtime_error on error.
*/
@@ -38,8 +36,4 @@ gcc_pure gcc_nonnull_all
int
IcuCollate(const char *a, const char *b) noexcept;
gcc_nonnull_all
AllocatedString<char>
IcuCaseFold(const char *src);
#endif

66
src/lib/icu/Compare.cxx Normal file

@@ -0,0 +1,66 @@
/*
* Copyright 2003-2017 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 "config.h"
#include "Compare.hxx"
#include "CaseFold.hxx"
#include "util/StringAPI.hxx"
#include <string.h>
#ifdef HAVE_ICU_CASE_FOLD
IcuCompare::IcuCompare(const char *_needle) noexcept
:needle(IcuCaseFold(_needle)) {}
#else
IcuCompare::IcuCompare(const char *_needle) noexcept
:needle(AllocatedString<>::Duplicate(_needle)) {}
#endif
bool
IcuCompare::operator==(const char *haystack) const noexcept
{
#ifdef HAVE_ICU_CASE_FOLD
return StringIsEqual(IcuCaseFold(haystack).c_str(), needle.c_str());
#else
return strcasecmp(haystack, needle.c_str());
#endif
}
bool
IcuCompare::IsIn(const char *haystack) const noexcept
{
#ifdef HAVE_ICU_CASE_FOLD
return StringFind(IcuCaseFold(haystack).c_str(),
needle.c_str()) != nullptr;
#elif defined(HAVE_STRCASESTR)
return strcasestr(haystack, needle.c_str()) != nullptr;
#else
/* poor man's strcasestr() */
for (const size_t length = strlen(needle.c_str());
*haystack != 0; ++haystack)
if (strncasecmp(haystack, needle.c_str(), length) == 0)
return true;
return false;
#endif
}

55
src/lib/icu/Compare.hxx Normal file

@@ -0,0 +1,55 @@
/*
* Copyright 2003-2017 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_ICU_COMPARE_HXX
#define MPD_ICU_COMPARE_HXX
#include "check.h"
#include "Compiler.h"
#include "util/AllocatedString.hxx"
/**
* This class can compare one string ("needle") with lots of other
* strings ("haystacks") efficiently, ignoring case. With some
* configurations, it can prepare a case-folded version of the needle.
*/
class IcuCompare {
AllocatedString<> needle;
public:
IcuCompare():needle(nullptr) {}
explicit IcuCompare(const char *needle) noexcept;
IcuCompare(IcuCompare &&) = default;
IcuCompare &operator=(IcuCompare &&) = default;
gcc_pure
operator bool() const noexcept {
return !needle.IsNull();
}
gcc_pure
bool operator==(const char *haystack) const noexcept;
gcc_pure
bool IsIn(const char *haystack) const noexcept;
};
#endif

@@ -40,7 +40,7 @@ public:
return *(UpnpCallback *)cookie;
}
virtual int Invoke(Upnp_EventType et, void *evp) = 0;
virtual int Invoke(Upnp_EventType et, const void *evp) = 0;
};
#endif

@@ -33,7 +33,12 @@ static unsigned upnp_client_ref;
static UpnpClient_Handle upnp_client_handle;
static int
UpnpClientCallback(Upnp_EventType et, void *evp, void *cookie)
UpnpClientCallback(Upnp_EventType et,
#if UPNP_VERSION >= 10800
const
#endif
void *evp,
void *cookie)
{
if (cookie == nullptr)
/* this is the cookie passed to UpnpRegisterClient();

69
src/lib/upnp/Compat.hxx Normal file

@@ -0,0 +1,69 @@
/*
* Copyright 2003-2017 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_UPNP_COMPAT_HXX
#define MPD_UPNP_COMPAT_HXX
#include <upnp/upnp.h>
#if UPNP_VERSION < 10800
#include "Compiler.h"
/* emulate the libupnp 1.8 API with older versions */
using UpnpDiscovery = Upnp_Discovery;
gcc_pure
static inline int
UpnpDiscovery_get_Expires(const UpnpDiscovery *disco) noexcept
{
return disco->Expires;
}
gcc_pure
static inline const char *
UpnpDiscovery_get_DeviceID_cstr(const UpnpDiscovery *disco) noexcept
{
return disco->DeviceId;
}
gcc_pure
static inline const char *
UpnpDiscovery_get_DeviceType_cstr(const UpnpDiscovery *disco) noexcept
{
return disco->DeviceType;
}
gcc_pure
static inline const char *
UpnpDiscovery_get_ServiceType_cstr(const UpnpDiscovery *disco) noexcept
{
return disco->ServiceType;
}
gcc_pure
static inline const char *
UpnpDiscovery_get_Location_cstr(const UpnpDiscovery *disco) noexcept
{
return disco->Location;
}
#endif
#endif

@@ -153,10 +153,10 @@ UPnPDeviceDirectory::Explore(void *ctx)
}
inline int
UPnPDeviceDirectory::OnAlive(Upnp_Discovery *disco)
UPnPDeviceDirectory::OnAlive(const UpnpDiscovery *disco)
{
if (isMSDevice(disco->DeviceType) ||
isCDService(disco->ServiceType)) {
if (isMSDevice(UpnpDiscovery_get_DeviceType_cstr(disco)) ||
isCDService(UpnpDiscovery_get_ServiceType_cstr(disco))) {
DiscoveredTask *tp = new DiscoveredTask(disco);
if (queue.put(tp))
return UPNP_E_FINISH;
@@ -166,12 +166,12 @@ UPnPDeviceDirectory::OnAlive(Upnp_Discovery *disco)
}
inline int
UPnPDeviceDirectory::OnByeBye(Upnp_Discovery *disco)
UPnPDeviceDirectory::OnByeBye(const UpnpDiscovery *disco)
{
if (isMSDevice(disco->DeviceType) ||
isCDService(disco->ServiceType)) {
if (isMSDevice(UpnpDiscovery_get_DeviceType_cstr(disco)) ||
isCDService(UpnpDiscovery_get_ServiceType_cstr(disco))) {
// Device signals it is going off.
LockRemove(disco->DeviceId);
LockRemove(UpnpDiscovery_get_DeviceID_cstr(disco));
}
return UPNP_E_SUCCESS;
@@ -182,19 +182,19 @@ UPnPDeviceDirectory::OnByeBye(Upnp_Discovery *disco)
// Example: ContentDirectories appearing and disappearing from the network
// We queue a task for our worker thread(s)
int
UPnPDeviceDirectory::Invoke(Upnp_EventType et, void *evp)
UPnPDeviceDirectory::Invoke(Upnp_EventType et, const void *evp)
{
switch (et) {
case UPNP_DISCOVERY_SEARCH_RESULT:
case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
{
Upnp_Discovery *disco = (Upnp_Discovery *)evp;
auto *disco = (const UpnpDiscovery *)evp;
return OnAlive(disco);
}
case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
{
Upnp_Discovery *disco = (Upnp_Discovery *)evp;
auto *disco = (const UpnpDiscovery *)evp;
return OnByeBye(disco);
}

@@ -20,6 +20,7 @@
#ifndef _UPNPPDISC_H_X_INCLUDED_
#define _UPNPPDISC_H_X_INCLUDED_
#include "Compat.hxx"
#include "Callback.hxx"
#include "Device.hxx"
#include "WorkQueue.hxx"
@@ -34,6 +35,10 @@
#include <memory>
#include <chrono>
#if UPNP_VERSION < 10800
#define UpnpDiscovery Upnp_Discovery
#endif
class ContentDirectoryService;
class UPnPDiscoveryListener {
@@ -59,10 +64,10 @@ class UPnPDeviceDirectory final : UpnpCallback {
std::string device_id;
std::chrono::steady_clock::duration expires;
DiscoveredTask(const Upnp_Discovery *disco)
:url(disco->Location),
device_id(disco->DeviceId),
expires(std::chrono::seconds(disco->Expires)) {}
DiscoveredTask(const UpnpDiscovery *disco)
:url(UpnpDiscovery_get_Location_cstr(disco)),
device_id(UpnpDiscovery_get_DeviceID_cstr(disco)),
expires(std::chrono::seconds(UpnpDiscovery_get_Expires(disco))) {}
};
/**
@@ -153,11 +158,11 @@ private:
static void *Explore(void *);
void Explore();
int OnAlive(Upnp_Discovery *disco);
int OnByeBye(Upnp_Discovery *disco);
int OnAlive(const UpnpDiscovery *disco);
int OnByeBye(const UpnpDiscovery *disco);
/* virtual methods from class UpnpCallback */
virtual int Invoke(Upnp_EventType et, void *evp) override;
virtual int Invoke(Upnp_EventType et, const void *evp) override;
};

@@ -271,16 +271,15 @@ try {
inline bool
AudioOutput::PlayChunk()
{
if (tags) {
const auto *tag = source.ReadTag();
if (tag != nullptr) {
const ScopeUnlock unlock(mutex);
try {
ao_plugin_send_tag(this, *tag);
} catch (const std::runtime_error &e) {
FormatError(e, "Failed to send tag to \"%s\" [%s]",
name, plugin.name);
}
// ensure pending tags are flushed in all cases
const auto *tag = source.ReadTag();
if (tags && tag != nullptr) {
const ScopeUnlock unlock(mutex);
try {
ao_plugin_send_tag(this, *tag);
} catch (const std::runtime_error &e) {
FormatError(e, "Failed to send tag to \"%s\" [%s]",
name, plugin.name);
}
}

@@ -50,7 +50,7 @@ class SndioOutput {
AudioOutput base;
const char *const device;
const unsigned buffer_time; /* in ms */
struct sio_hdl *sio_hdl;
struct sio_hdl *hdl;
public:
SndioOutput(const ConfigBlock &block);
@@ -80,16 +80,14 @@ SndioOutput::Create(const ConfigBlock &block)
static bool
sndio_test_default_device()
{
struct sio_hdl *sio_hdl;
sio_hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
if (!sio_hdl) {
auto *hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
if (!hdl) {
FormatError(sndio_output_domain,
"Error opening default sndio device");
"Error opening default sndio device");
return false;
}
sio_close(sio_hdl);
sio_close(hdl);
return true;
}
@@ -99,8 +97,8 @@ SndioOutput::Open(AudioFormat &audio_format)
struct sio_par par;
unsigned bits, rate, chans;
sio_hdl = sio_open(device, SIO_PLAY, 0);
if (!sio_hdl)
hdl = sio_open(device, SIO_PLAY, 0);
if (!hdl)
throw std::runtime_error("Failed to open default sndio device");
switch (audio_format.format) {
@@ -130,9 +128,9 @@ SndioOutput::Open(AudioFormat &audio_format)
par.le = SIO_LE_NATIVE;
par.appbufsz = rate * buffer_time / 1000;
if (!sio_setpar(sio_hdl, &par) ||
!sio_getpar(sio_hdl, &par)) {
sio_close(sio_hdl);
if (!sio_setpar(hdl, &par) ||
!sio_getpar(hdl, &par)) {
sio_close(hdl);
throw std::runtime_error("Failed to set/get audio params");
}
@@ -142,12 +140,12 @@ SndioOutput::Open(AudioFormat &audio_format)
par.pchan != chans ||
par.sig != 1 ||
par.le != SIO_LE_NATIVE) {
sio_close(sio_hdl);
sio_close(hdl);
throw std::runtime_error("Requested audio params cannot be satisfied");
}
if (!sio_start(sio_hdl)) {
sio_close(sio_hdl);
if (!sio_start(hdl)) {
sio_close(hdl);
throw std::runtime_error("Failed to start audio device");
}
}
@@ -155,7 +153,7 @@ SndioOutput::Open(AudioFormat &audio_format)
void
SndioOutput::Close()
{
sio_close(sio_hdl);
sio_close(hdl);
}
size_t
@@ -163,8 +161,8 @@ SndioOutput::Play(const void *chunk, size_t size)
{
size_t n;
n = sio_write(sio_hdl, chunk, size);
if (n == 0 && sio_eof(sio_hdl) != 0)
n = sio_write(hdl, chunk, size);
if (n == 0 && sio_eof(hdl) != 0)
throw std::runtime_error("sndio write failed");
return n;
}

@@ -468,6 +468,7 @@ HttpdOutput::SendTag(const Tag &tag)
try {
encoder->SendTag(tag);
encoder->Flush();
} catch (const std::runtime_error &) {
/* ignore */
}

@@ -32,6 +32,7 @@
#include "output/MultipleOutputs.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
#include "system/PeriodClock.hxx"
#include "util/Domain.hxx"
#include "thread/Name.hxx"
#include "Log.hxx"
@@ -146,6 +147,8 @@ class Player {
*/
SongTime elapsed_time;
PeriodClock throttle_silence_log;
public:
Player(PlayerControl &_pc, DecoderControl &_dc,
MusicBuffer &_buffer)
@@ -934,6 +937,8 @@ Player::SongBorder()
{
FormatDefault(player_domain, "played \"%s\"", song->GetURI());
throttle_silence_log.Reset();
ReplacePipe(dc.pipe);
pc.outputs.SongBorder();
@@ -1095,6 +1100,10 @@ Player::Run()
/* the decoder is too busy and hasn't provided
new PCM data in time: send silence (if the
output pipe is empty) */
if (throttle_silence_log.CheckUpdate(std::chrono::seconds(5)))
FormatWarning(player_domain, "Decoder is too slow; playing silence to avoid xrun");
if (!SendSilence())
break;
}

@@ -195,7 +195,7 @@ playlist_list_open_stream_mime2(InputStreamPtr &&is, const char *mime)
/* rewind the stream, so each plugin gets a
fresh start */
try {
is->Rewind();
is->LockRewind();
} catch (const std::runtime_error &) {
}
@@ -239,7 +239,7 @@ playlist_list_open_stream_suffix(InputStreamPtr &&is, const char *suffix)
/* rewind the stream, so each plugin gets a
fresh start */
try {
is->Rewind();
is->LockRewind();
} catch (const std::runtime_error &) {
}

@@ -310,8 +310,7 @@ playlist::SetRandom(PlayerControl &pc, bool status)
playlist is played after that */
unsigned current_order =
queue.PositionToOrder(current_position);
queue.MoveOrder(current_order, 0);
current = 0;
current = queue.MoveOrder(current_order, 0);
} else
current = -1;
} else

@@ -356,6 +356,18 @@ public:
}
void SetConsume(bool new_value);
private:
/**
* Prepare a manual song change: move the given song to the
* current playback order. This is done to avoid skipping
* upcoming songs in the order list. The newly selected song
* shall be inserted in the order list, and the rest shall be
* played after that as previously planned.
*
* @return the new order number of the given song
*/
unsigned MoveOrderToCurrent(unsigned old_order);
};
#endif

@@ -56,6 +56,30 @@ playlist::Stop(PlayerControl &pc)
}
}
unsigned
playlist::MoveOrderToCurrent(unsigned old_order)
{
if (!queue.random)
/* no-op because there is no order list */
return old_order;
if (playing) {
/* already playing: move the specified song after the
current one (because the current one has already
been playing and shall not be played again) */
return queue.MoveOrderAfter(old_order, current);
} else if (current >= 0) {
/* not playing: move the specified song before the
current one, so it will be played eventually */
return queue.MoveOrderBefore(old_order, current);
} else {
/* not playing anything: move the specified song to
the front */
queue.SwapOrders(old_order, 0);
return 0;
}
}
void
playlist::PlayPosition(PlayerControl &pc, int song)
{
@@ -90,13 +114,7 @@ playlist::PlayPosition(PlayerControl &pc, int song)
number, because random mode is enabled */
i = queue.PositionToOrder(song);
if (!playing)
current = 0;
/* swap the new song with the previous "current" one,
so playback continues as planned */
queue.SwapOrders(i, current);
i = current;
i = MoveOrderToCurrent(i);
}
stop_on_error = false;
@@ -205,6 +223,8 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
/* seeking is not within the current song - prepare
song change */
i = MoveOrderToCurrent(i);
playing = true;
current = i;

@@ -195,7 +195,7 @@ Queue::MoveRange(unsigned start, unsigned end, unsigned to) noexcept
}
}
void
unsigned
Queue::MoveOrder(unsigned from_order, unsigned to_order) noexcept
{
assert(from_order < length);
@@ -212,6 +212,25 @@ Queue::MoveOrder(unsigned from_order, unsigned to_order) noexcept
}
order[to_order] = from_position;
return to_order;
}
unsigned
Queue::MoveOrderBefore(unsigned from_order, unsigned to_order) noexcept
{
/* if "from_order" comes before "to_order", then the new
position is "to_order-1"; otherwise the "to_order" song is
moved one ahead */
return MoveOrder(from_order, to_order - (from_order < to_order));
}
unsigned
Queue::MoveOrderAfter(unsigned from_order, unsigned to_order) noexcept
{
/* if "from_order" comes after "to_order", then the new
position is "to_order+1"; otherwise the "to_order" song is
moved one back */
return MoveOrder(from_order, to_order + (from_order > to_order));
}
void

@@ -284,8 +284,28 @@ struct Queue {
/**
* Moves a song to a new position in the "order" list.
*
* @return to_order
*/
void MoveOrder(unsigned from_order, unsigned to_order) noexcept;
unsigned MoveOrder(unsigned from_order, unsigned to_order) noexcept;
/**
* Moves a song to a new position in the "order" list before
* the given one.
*
* @return the new order number of the given "from" song
*/
unsigned MoveOrderBefore(unsigned from_order,
unsigned to_order) noexcept;
/**
* Moves a song to a new position in the "order" list after
* the given one.
*
* @return the new order number of the given "from" song
*/
unsigned MoveOrderAfter(unsigned from_order,
unsigned to_order) noexcept;
/**
* Moves a song to a new position.

@@ -247,6 +247,22 @@ ParseU64(const char *s, size_t length)
return ParseU64(std::string(s, length).c_str());
}
gcc_pure
static bool
IsXmlContentType(const char *content_type) noexcept
{
return StringStartsWith(content_type, "text/xml") ||
StringStartsWith(content_type, "application/xml");
}
gcc_pure
static bool
IsXmlContentType(const std::multimap<std::string, std::string> &headers) noexcept
{
auto i = headers.find("content-type");
return i != headers.end() && IsXmlContentType(i->second.c_str());
}
/**
* A WebDAV PROPFIND request. Each "response" element will be passed
* to OnDavResponse() (to be implemented by a derived class).
@@ -308,9 +324,7 @@ private:
throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
status);
auto i = headers.find("content-type");
if (i == headers.end() ||
strncmp(i->second.c_str(), "text/xml", 8) != 0)
if (!IsXmlContentType(headers))
throw std::runtime_error("Unexpected Content-Type from WebDAV server");
}

@@ -37,7 +37,7 @@ ArgParserTest::TestRange()
try {
range = ParseCommandArgRange("-2");
CPPUNIT_ASSERT(false);
} catch (ProtocolError) {
} catch (const ProtocolError &) {
CPPUNIT_ASSERT(true);
}
}

@@ -51,6 +51,11 @@ class CrossGccToolchain:
self.strip = os.path.join(toolchain_bin, arch + '-strip')
common_flags = ''
if not x64:
# enable SSE support which is required for LAME
common_flags += ' -march=pentium3'
self.cflags = '-O2 -g ' + common_flags
self.cxxflags = '-O2 -g ' + common_flags
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include')