Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
323231d1dd | ||
![]() |
714011c81e | ||
![]() |
952ff4207b | ||
![]() |
150b16ec2c | ||
![]() |
c98bc4a243 | ||
![]() |
014f8cd693 | ||
![]() |
aea37e46e3 | ||
![]() |
31ab78ae8e | ||
![]() |
f82e1453e4 | ||
![]() |
a2b77c8813 | ||
![]() |
18add29472 | ||
![]() |
b111a8fe8d | ||
![]() |
3b23cf0258 | ||
![]() |
28e864e096 | ||
![]() |
1de19b921a | ||
![]() |
ff162b5a03 | ||
![]() |
d8e4705dd4 | ||
![]() |
338e1f5926 | ||
![]() |
a7fdfa08e1 | ||
![]() |
9703a401c5 | ||
![]() |
753a2aa462 | ||
![]() |
10990a0684 | ||
![]() |
91254e9211 | ||
![]() |
0f79287b04 | ||
![]() |
f2fac77d8c | ||
![]() |
81b7373637 | ||
![]() |
fa67c2548a | ||
![]() |
ea80587ddb | ||
![]() |
828f5f8384 | ||
![]() |
1295a1272a | ||
![]() |
66646d9276 | ||
![]() |
d0497dba92 | ||
![]() |
42914e8227 | ||
![]() |
59b49b7881 | ||
![]() |
5620f16330 | ||
![]() |
be024d4ad7 | ||
![]() |
75c740fe2b | ||
![]() |
6c8d86bb90 | ||
![]() |
b253a6b71e | ||
![]() |
ca7b4df812 | ||
![]() |
bc8dd57236 | ||
![]() |
f4f461b8bb | ||
![]() |
cbb9b6957f | ||
![]() |
f6b56c9317 | ||
![]() |
3717fb6c8d | ||
![]() |
f6abbc01bd |
Makefile.amNEWSconfigure.ac
doc
src
PlaylistFile.cxxPlaylistSave.cxxSongFilter.cxxSongFilter.hxx
db
decoder
encoder
plugins
input
plugins
lib
icu
upnp
output
player
playlist
queue
storage
plugins
test
win32
@@ -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
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
|
||||
|
10
configure.ac
10
configure.ac
@@ -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>
|
||||
|
58
doc/user.xml
58
doc/user.xml
@@ -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
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
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
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
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
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')
|
||||
|
Reference in New Issue
Block a user