Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a40510c241 | ||
![]() |
ac8dce6599 | ||
![]() |
190d525099 | ||
![]() |
1b6666fa39 | ||
![]() |
1dd01c99e8 | ||
![]() |
d50b30a498 | ||
![]() |
42a3a87f13 | ||
![]() |
9dfedbe619 | ||
![]() |
88957b4c9d | ||
![]() |
b2f2c9322b | ||
![]() |
3be2051808 | ||
![]() |
ff32b0dc9b | ||
![]() |
c1869a11af | ||
![]() |
e22a4fdba4 | ||
![]() |
29a7b2c5b5 | ||
![]() |
3b6c285c2a | ||
![]() |
575d1786af | ||
![]() |
bc1c927952 | ||
![]() |
f95bc85f91 | ||
![]() |
4015195314 | ||
![]() |
c3d883c6cb | ||
![]() |
097e30321b | ||
![]() |
b6ddeaacf2 | ||
![]() |
b0c60ec124 | ||
![]() |
f3b788703e | ||
![]() |
4bb83781e8 | ||
![]() |
a73195b7cc | ||
![]() |
1bd00b8a9a | ||
![]() |
d84eaeafc5 | ||
![]() |
20ae84bff9 | ||
![]() |
7372c931b3 | ||
![]() |
29e1b6e465 | ||
![]() |
eda06993f8 | ||
![]() |
4b30ef1cf2 | ||
![]() |
e92e5e8eb8 | ||
![]() |
4e5271fcdf | ||
![]() |
3c55487a16 | ||
![]() |
76a1cae5d8 | ||
![]() |
81a97315e3 | ||
![]() |
53c14d97a6 | ||
![]() |
69a82eec17 | ||
![]() |
45cadef22f | ||
![]() |
0a033fb10a | ||
![]() |
591afa0647 | ||
![]() |
05eac20ffe | ||
![]() |
38d263ac19 | ||
![]() |
f71c204eef | ||
![]() |
51147203be | ||
![]() |
a931686317 | ||
![]() |
5bd322bdcf | ||
![]() |
bb097109f0 |
NEWS
android
configure.acdoc
python/build
src
win32
28
NEWS
28
NEWS
@@ -1,3 +1,31 @@
|
||||
ver 0.20.6 (2017/03/10)
|
||||
* input
|
||||
- curl: fix headers after HTTP redirect to Shoutcast server
|
||||
* decoder
|
||||
- ffmpeg: re-enable as fallback
|
||||
- mpcdec: fix crash (division by zero) after seeking
|
||||
- sidplay: make compatible with libsidplayfp < 1.8
|
||||
* fix stream tags after automatic song change
|
||||
* workaround for GCC 4.9.4 / libstdc++ bug (build failure)
|
||||
|
||||
ver 0.20.5 (2017/02/20)
|
||||
* tags
|
||||
- id3: fix memory leak on corrupt ID3 tags
|
||||
* decoder
|
||||
- sidplay: don't require libsidutils when building with libsidplayfp
|
||||
* output
|
||||
- httpd: fix two buffer overflows in IcyMetaData length calculation
|
||||
* mixer
|
||||
- alsa: fix crash bug
|
||||
|
||||
ver 0.20.4 (2017/02/01)
|
||||
* input
|
||||
- nfs: fix freeze after reconnect
|
||||
* output
|
||||
- sndio: work around a libroar C++ incompatibility
|
||||
* workaround for GCC 4.9 "constexpr" bug
|
||||
* fix FreeBSD build failure
|
||||
|
||||
ver 0.20.3 (2017/01/25)
|
||||
* protocol
|
||||
- "playlistadd" creates new playlist if it does not exist, as documented
|
||||
|
@@ -87,9 +87,14 @@ class AndroidNdkToolchain:
|
||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||
self.is_windows = False
|
||||
|
||||
libstdcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/gnu-libstdc++', gcc_version)
|
||||
libstdcxx_cppflags = '-isystem ' + os.path.join(libstdcxx_path, 'include') + ' -isystem ' + os.path.join(libstdcxx_path, 'libs', android_abi, 'include')
|
||||
libstdcxx_ldadd = os.path.join(libstdcxx_path, 'libs', android_abi, 'libgnustl_static.a')
|
||||
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
||||
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
||||
|
||||
libstdcxx_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
|
||||
|
||||
if self.is_armv7:
|
||||
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
|
||||
|
||||
if use_cxx:
|
||||
self.libs += ' ' + libstdcxx_ldadd
|
||||
|
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.20.3, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.20.6, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=20
|
||||
VERSION_REVISION=3
|
||||
VERSION_REVISION=6
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -992,7 +992,7 @@ AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enab
|
||||
dnl --------------------------------- sidplay ---------------------------------
|
||||
if test x$enable_sidplay != xno; then
|
||||
dnl Check for libsidplayfp first
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp libsidutils],
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp],
|
||||
[found_sidplayfp=yes],
|
||||
[found_sidplayfp=no])
|
||||
found_sidplay=$found_sidplayfp
|
||||
|
@@ -55,7 +55,8 @@
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>track</varname>: the track number within the album.
|
||||
<varname>track</varname>: the decimal track number within the
|
||||
album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
@@ -103,7 +104,8 @@
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>disc</varname>: the disc number in a multi-disc album.
|
||||
<varname>disc</varname>: the decimal disc number in a multi-disc
|
||||
album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
|
85
doc/user.xml
85
doc/user.xml
@@ -135,6 +135,91 @@ apt-get install g++ \
|
||||
</para>
|
||||
|
||||
<programlisting>make install</programlisting>
|
||||
|
||||
<section id="windows_build">
|
||||
<title>Compiling for Windows</title>
|
||||
|
||||
<para>
|
||||
Even though it does not "feel" like a Windows application,
|
||||
<application>MPD</application> works well under Windows.
|
||||
Its build process follows the "Linux style", and may seem
|
||||
awkward for Windows people (who are not used to compiling
|
||||
their software, anyway).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Basically, there are three ways to compile
|
||||
<application>MPD</application> for Windows:
|
||||
</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Build on Windows for Windows. All you need to do is
|
||||
described above already: configure and make.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For Windows users, this is kind of unusual, because few
|
||||
Windows users have a GNU toolchain and a UNIX shell
|
||||
installed.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Build on Linux for Windows. This is described above
|
||||
already: configure and make. You need the <ulink
|
||||
url="https://mingw-w64.org/"><application>mingw-w64</application>
|
||||
cross compiler</ulink>. Pass
|
||||
<parameter>--host=i686-w64-mingw32</parameter> (32 bit)
|
||||
or <parameter>--host=x86_64-w64-mingw32</parameter> (64
|
||||
bit) to configure.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This is somewhat natural for Linux users. Many
|
||||
distributions have <application>mingw-w64</application>
|
||||
packages. The remaining difficulty here is installing
|
||||
all the external libraries. And
|
||||
<application>MPD</application> usually needs many,
|
||||
making this method cumbersome for the casual user.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Build on Linux for Windows using the
|
||||
<application>MPD</application>'s library build script.
|
||||
</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>
|
||||
This section is about the latter.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Just like with the native build, unpack the
|
||||
<application>MPD</application> source tarball and change
|
||||
into the directory. Then, instead of
|
||||
<command>./configure</command>, type:
|
||||
</para>
|
||||
|
||||
<programlisting>./win32/build.py --64</programlisting>
|
||||
|
||||
<para>
|
||||
This downloads various library sources, and then configures
|
||||
and builds <application>MPD</application> (for x64; to build
|
||||
a 32 bit binary, pass <parameter>--32</parameter>). The
|
||||
resulting EXE files is linked statically, i.e. it contains
|
||||
all the libraries already, and you do not need carry DLLs
|
||||
around. It is large, but easy to use. If you wish to have
|
||||
a small <filename>mpd.exe</filename> with DLLs, you need to
|
||||
compile manually, without the <filename>build.py</filename>
|
||||
script.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="systemd_socket">
|
||||
|
@@ -19,8 +19,8 @@ libvorbis = AutotoolsProject(
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/opus/opus-1.1.3.tar.gz',
|
||||
'32bbb6b557fe1b6066adc0ae1f08b629',
|
||||
'http://downloads.xiph.org/releases/opus/opus-1.1.4.tar.gz',
|
||||
'9122b6b380081dd2665189f97bfd777f04f92dc3ab6698eea1dbb27ad59d8692',
|
||||
'lib/libopus.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
)
|
||||
@@ -58,8 +58,8 @@ libmad = AutotoolsProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-3.2.2.tar.xz',
|
||||
'3f01bd1fe1a17a277f8c84869e5d9192b4b978cb660872aa2b54c3cc8a2fedfc',
|
||||
'http://ffmpeg.org/releases/ffmpeg-3.2.4.tar.xz',
|
||||
'6e38ff14f080c98b58cf5967573501b8cb586e3a173b591f3807d8f0660daf7a',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
@@ -141,9 +141,9 @@ Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume)
|
||||
void
|
||||
Partition::OnGlobalEvent(unsigned mask)
|
||||
{
|
||||
if ((mask & TAG_MODIFIED) != 0)
|
||||
TagModified();
|
||||
|
||||
if ((mask & SYNC_WITH_PLAYER) != 0)
|
||||
SyncWithPlayer();
|
||||
|
||||
if ((mask & TAG_MODIFIED) != 0)
|
||||
TagModified();
|
||||
}
|
||||
|
@@ -29,6 +29,29 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#define GLIBCXX_490 20140422
|
||||
#define GLIBCXX_491 20140716
|
||||
#define GLIBCXX_492 20141030
|
||||
#define GLIBCXX_492_Debian_9 20141220
|
||||
#define GLIBCXX_493 20150626
|
||||
#define GLIBCXX_494 20160803
|
||||
#define GLIBCXX_49X_NDK_r13b 20150123
|
||||
|
||||
/* the big mess attempts to detect whether we're compiling with
|
||||
libstdc++ 4.9.x; __GLIBCXX__ is a date tag and cannot be used to
|
||||
check the major version; and just checking the compiler version
|
||||
isn't enough, because somebody could use an old libstdc++ with
|
||||
clang - SIGH! */
|
||||
#if GCC_OLDER_THAN(5,0) || (defined(__GLIBCXX__) && \
|
||||
(__GLIBCXX__ == GLIBCXX_490 || __GLIBCXX__ == GLIBCXX_491 || \
|
||||
__GLIBCXX__ == GLIBCXX_492 || \
|
||||
__GLIBCXX__ == GLIBCXX_492_Debian_9 || \
|
||||
__GLIBCXX__ == GLIBCXX_493 || \
|
||||
__GLIBCXX__ == GLIBCXX_494 || \
|
||||
__GLIBCXX__ == GLIBCXX_49X_NDK_r13b))
|
||||
#define GLIBCXX_49X
|
||||
#endif
|
||||
|
||||
gcc_const
|
||||
static enum ack
|
||||
ToAck(PlaylistResult result)
|
||||
@@ -100,13 +123,13 @@ ToAck(std::exception_ptr ep)
|
||||
return ACK_ERROR_SYSTEM;
|
||||
} catch (const std::invalid_argument &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20151204
|
||||
#ifdef GLIBCXX_49X
|
||||
} catch (const std::exception &e) {
|
||||
#else
|
||||
} catch (...) {
|
||||
#endif
|
||||
try {
|
||||
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20151204
|
||||
#ifdef GLIBCXX_49X
|
||||
/* workaround for g++ 4.x: no overload for
|
||||
rethrow_exception(exception_ptr) */
|
||||
std::rethrow_if_nested(e);
|
||||
|
@@ -62,7 +62,7 @@ Print(Response &r, TagType group, const TagCountMap &m)
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
stats_visitor_song(SearchStats &stats, const LightSong &song)
|
||||
{
|
||||
stats.n_songs++;
|
||||
@@ -70,8 +70,6 @@ stats_visitor_song(SearchStats &stats, const LightSong &song)
|
||||
const auto duration = song.GetDuration();
|
||||
if (!duration.IsNegative())
|
||||
stats.total_duration += duration;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -94,7 +92,7 @@ CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
||||
{
|
||||
assert(song.tag != nullptr);
|
||||
@@ -103,8 +101,6 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
||||
if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST)
|
||||
/* fall back to "Artist" if no "AlbumArtist" was found */
|
||||
CollectGroupCounts(map, TAG_ARTIST, tag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -27,13 +27,12 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
static bool
|
||||
static void
|
||||
AddSong(const Storage &storage, const char *playlist_path_utf8,
|
||||
const LightSong &song)
|
||||
{
|
||||
spl_append_song(playlist_path_utf8,
|
||||
DatabaseDetachSong(storage, song));
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -49,16 +49,14 @@ PrintDirectoryURI(Response &r, bool base, const LightDirectory &directory)
|
||||
ApplyBaseFlag(directory.GetPath(), base));
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintDirectoryBrief(Response &r, bool base, const LightDirectory &directory)
|
||||
{
|
||||
if (!directory.IsRoot())
|
||||
PrintDirectoryURI(r, base, directory);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
|
||||
{
|
||||
if (!directory.IsRoot()) {
|
||||
@@ -67,8 +65,6 @@ PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
|
||||
if (directory.mtime > 0)
|
||||
time_print(r, "Last-Modified", directory.mtime);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -96,7 +92,7 @@ print_playlist_in_directory(Response &r, bool base,
|
||||
directory->GetPath(), name_utf8);
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintSongBrief(Response &r, Partition &partition,
|
||||
bool base, const LightSong &song)
|
||||
{
|
||||
@@ -106,11 +102,9 @@ PrintSongBrief(Response &r, Partition &partition,
|
||||
/* this song file has an embedded CUE sheet */
|
||||
print_playlist_in_directory(r, base,
|
||||
song.directory, song.uri);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintSongFull(Response &r, Partition &partition,
|
||||
bool base, const LightSong &song)
|
||||
{
|
||||
@@ -120,21 +114,18 @@ PrintSongFull(Response &r, Partition &partition,
|
||||
/* this song file has an embedded CUE sheet */
|
||||
print_playlist_in_directory(r, base,
|
||||
song.directory, song.uri);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintPlaylistBrief(Response &r, bool base,
|
||||
const PlaylistInfo &playlist,
|
||||
const LightDirectory &directory)
|
||||
{
|
||||
print_playlist_in_directory(r, base,
|
||||
&directory, playlist.name.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintPlaylistFull(Response &r, bool base,
|
||||
const PlaylistInfo &playlist,
|
||||
const LightDirectory &directory)
|
||||
@@ -144,8 +135,6 @@ PrintPlaylistFull(Response &r, bool base,
|
||||
|
||||
if (playlist.mtime > 0)
|
||||
time_print(r, "Last-Modified", playlist.mtime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -191,15 +180,13 @@ db_selection_print(Response &r, Partition &partition,
|
||||
0, std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song)
|
||||
{
|
||||
song_print_uri(r, partition, song);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintUniqueTag(Response &r, TagType tag_type,
|
||||
const Tag &tag)
|
||||
{
|
||||
@@ -211,8 +198,6 @@ PrintUniqueTag(Response &r, TagType tag_type,
|
||||
if (item.type != tag_type)
|
||||
r.Format("%s: %s\n",
|
||||
tag_item_names[item.type], item.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -27,14 +27,13 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
static bool
|
||||
static void
|
||||
AddToQueue(Partition &partition, const LightSong &song)
|
||||
{
|
||||
const Storage &storage = *partition.instance.storage;
|
||||
partition.playlist.AppendSong(partition.pc,
|
||||
DatabaseDetachSong(storage,
|
||||
song));
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -67,15 +67,13 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
|
||||
const LightSong &song)
|
||||
{
|
||||
++stats.song_count;
|
||||
|
||||
StatsVisitTag(stats, artists, albums, *song.tag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
|
@@ -25,6 +25,13 @@
|
||||
#include "db/Interface.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
/* workaround for "error: incomplete type 'PlaylistInfo' used in type
|
||||
trait expression" with libc++ version 3900 (from Android NDK
|
||||
r13b) */
|
||||
#include "db/PlaylistInfo.hxx"
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
struct PrefixedLightDirectory : LightDirectory {
|
||||
|
@@ -235,7 +235,7 @@ decoder_run_stream_fallback(DecoderBridge &bridge, InputStream &is)
|
||||
{
|
||||
const struct DecoderPlugin *plugin;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#ifdef ENABLE_FFMPEG
|
||||
plugin = decoder_plugin_from_name("ffmpeg");
|
||||
#else
|
||||
plugin = decoder_plugin_from_name("mad");
|
||||
|
@@ -207,6 +207,15 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
if (frame.bits == -1)
|
||||
break;
|
||||
|
||||
if (frame.samples <= 0) {
|
||||
/* empty frame - this has been observed to
|
||||
happen spuriously after seeking; skip this
|
||||
obscure frame, and hope libmpcdec
|
||||
recovers */
|
||||
cmd = client.GetCommand();
|
||||
continue;
|
||||
}
|
||||
|
||||
mpc_uint32_t ret = frame.samples;
|
||||
ret *= info.channels;
|
||||
|
||||
|
@@ -50,6 +50,10 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#define LIBSIDPLAYFP_VERSION GCC_MAKE_VERSION(LIBSIDPLAYFP_VERSION_MAJ, LIBSIDPLAYFP_VERSION_MIN, LIBSIDPLAYFP_VERSION_LEV)
|
||||
#endif
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
static constexpr Domain sidplay_domain("sidplay");
|
||||
@@ -285,7 +289,11 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#if LIBSIDPLAYFP_VERSION >= GCC_MAKE_VERSION(1,8,0)
|
||||
const bool stereo = tune.getInfo()->sidChips() >= 2;
|
||||
#else
|
||||
const bool stereo = tune.getInfo()->isStereo();
|
||||
#endif
|
||||
#else
|
||||
const bool stereo = tune.isStereo();
|
||||
#endif
|
||||
|
@@ -34,6 +34,8 @@
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#define ERRORLEN 80
|
||||
|
@@ -79,7 +79,7 @@ private:
|
||||
void
|
||||
BlockingCall(EventLoop &loop, std::function<void()> &&f)
|
||||
{
|
||||
if (loop.IsInside()) {
|
||||
if (loop.IsInsideOrNull()) {
|
||||
/* we're already inside the loop - we can simply call
|
||||
the function */
|
||||
f();
|
||||
|
@@ -222,8 +222,9 @@ EventLoop::Run()
|
||||
#ifndef NDEBUG
|
||||
assert(busy);
|
||||
assert(thread.IsInside());
|
||||
thread = ThreadId::Null();
|
||||
#endif
|
||||
|
||||
thread = ThreadId::Null();
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -212,12 +212,16 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
/**
|
||||
* Like IsInside(), but also returns true if the thread has
|
||||
* already ended (or was not started yet). This is useful for
|
||||
* code which may run during startup or shutdown, when events
|
||||
* are not yet/anymore handled.
|
||||
*/
|
||||
gcc_pure
|
||||
bool IsInsideOrNull() const {
|
||||
return thread.IsNull() || thread.IsInside();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* MAIN_NOTIFY_H */
|
||||
|
@@ -28,12 +28,18 @@
|
||||
#endif
|
||||
|
||||
MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop)
|
||||
:IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) {
|
||||
:IdleMonitor(_loop), TimeoutMonitor(_loop) {
|
||||
}
|
||||
|
||||
MultiSocketMonitor::~MultiSocketMonitor()
|
||||
void
|
||||
MultiSocketMonitor::Reset()
|
||||
{
|
||||
// TODO
|
||||
assert(GetEventLoop().IsInsideOrNull());
|
||||
|
||||
fds.clear();
|
||||
IdleMonitor::Cancel();
|
||||
TimeoutMonitor::Cancel();
|
||||
ready = refresh = false;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -96,7 +96,19 @@ class MultiSocketMonitor : IdleMonitor, TimeoutMonitor
|
||||
|
||||
friend class SingleFD;
|
||||
|
||||
bool ready, refresh;
|
||||
/**
|
||||
* DispatchSockets() should be called.
|
||||
*/
|
||||
bool ready = false;
|
||||
|
||||
/**
|
||||
* PrepareSockets() should be called.
|
||||
*
|
||||
* Note that this doesn't need to be initialized by the
|
||||
* constructor; this class is activated with the first
|
||||
* InvalidateSockets() call, which initializes this flag.
|
||||
*/
|
||||
bool refresh;
|
||||
|
||||
std::forward_list<SingleFD> fds;
|
||||
|
||||
@@ -107,11 +119,26 @@ public:
|
||||
static constexpr unsigned HANGUP = SocketMonitor::HANGUP;
|
||||
|
||||
MultiSocketMonitor(EventLoop &_loop);
|
||||
~MultiSocketMonitor();
|
||||
|
||||
using IdleMonitor::GetEventLoop;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Clear the socket list and disable all #EventLoop
|
||||
* registrations. Run this in the #EventLoop thread before
|
||||
* destroying this object.
|
||||
*
|
||||
* Later, this object can be reused and reactivated by calling
|
||||
* InvalidateSockets().
|
||||
*
|
||||
* Note that this class doesn't have a destructor which calls
|
||||
* this method, because this would be racy and thus pointless:
|
||||
* at the time ~MultiSocketMonitor() is called, our virtual
|
||||
* methods have been morphed to be pure again, and in the
|
||||
* meantime the #EventLoop thread could invoke those pure
|
||||
* methods.
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* Invalidate the socket list. A call to PrepareSockets() is
|
||||
* scheduled which will then update the list.
|
||||
@@ -121,12 +148,19 @@ public:
|
||||
IdleMonitor::Schedule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one socket to the list of monitored sockets.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void AddSocket(int fd, unsigned events) {
|
||||
fds.emplace_front(*this, fd, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all sockets.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void ClearSocketList();
|
||||
|
||||
@@ -135,6 +169,8 @@ public:
|
||||
* each one; its return value is the events bit mask. A
|
||||
* return value of 0 means the socket will be removed from the
|
||||
* list.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
template<typename E>
|
||||
void UpdateSocketList(E &&e) {
|
||||
@@ -157,15 +193,26 @@ public:
|
||||
/**
|
||||
* Replace the socket list with the given file descriptors.
|
||||
* The given pollfd array will be modified by this method.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void ReplaceSocketList(pollfd *pfds, unsigned n);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Override this method and update the socket registrations.
|
||||
* To do that, call AddSocket(), ClearSocketList(),
|
||||
* UpdateSocketList() and ReplaceSocketList().
|
||||
*
|
||||
* @return timeout or a negative value for no timeout
|
||||
*/
|
||||
virtual std::chrono::steady_clock::duration PrepareSockets() = 0;
|
||||
|
||||
/**
|
||||
* At least one socket is ready or the timeout has expired.
|
||||
* This method should be used to perform I/O.
|
||||
*/
|
||||
virtual void DispatchSockets() = 0;
|
||||
|
||||
private:
|
||||
|
@@ -98,12 +98,9 @@ public:
|
||||
}
|
||||
|
||||
~AlsaInputStream() {
|
||||
/* ClearSocketList must be called from within the
|
||||
IOThread; if we don't do it manually here, the
|
||||
~MultiSocketMonitor() will do it in the current
|
||||
thread */
|
||||
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
||||
ClearSocketList();
|
||||
MultiSocketMonitor::Reset();
|
||||
DeferredMonitor::Cancel();
|
||||
});
|
||||
|
||||
snd_pcm_close(capture_handle);
|
||||
@@ -181,7 +178,7 @@ AlsaInputStream::PrepareSockets()
|
||||
}
|
||||
|
||||
int count = snd_pcm_poll_descriptors_count(capture_handle);
|
||||
if (count < 0) {
|
||||
if (count <= 0) {
|
||||
ClearSocketList();
|
||||
return std::chrono::steady_clock::duration(-1);
|
||||
}
|
||||
|
@@ -398,7 +398,12 @@ CurlInputStream::SeekInternal(offset_type new_offset)
|
||||
/* send the "Range" header */
|
||||
|
||||
if (offset > 0) {
|
||||
sprintf(range, "%lld-", (long long)offset);
|
||||
#ifdef WIN32
|
||||
// TODO: what can we use on Windows to format 64 bit?
|
||||
sprintf(range, "%lu-", (long)offset);
|
||||
#else
|
||||
sprintf(range, "%llu-", (unsigned long long)offset);
|
||||
#endif
|
||||
request->SetOption(CURLOPT_RANGE, range);
|
||||
}
|
||||
}
|
||||
|
@@ -168,13 +168,23 @@ CurlRequest::Done(CURLcode result)
|
||||
}
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsResponseBoundaryHeader(StringView s)
|
||||
{
|
||||
return s.size > 5 && (memcmp(s.data, "HTTP/", 5) == 0 ||
|
||||
/* the proprietary "ICY 200 OK" is
|
||||
emitted by Shoutcast */
|
||||
memcmp(s.data, "ICY 2", 5) == 0);
|
||||
}
|
||||
|
||||
inline void
|
||||
CurlRequest::HeaderFunction(StringView s)
|
||||
{
|
||||
if (state > State::HEADERS)
|
||||
return;
|
||||
|
||||
if (s.size > 5 && memcmp(s.data, "HTTP/", 5) == 0) {
|
||||
if (IsResponseBoundaryHeader(s)) {
|
||||
/* this is the boundary to a new response, for example
|
||||
after a redirect */
|
||||
headers.clear();
|
||||
|
@@ -396,6 +396,17 @@ NfsConnection::ScheduleSocket()
|
||||
assert(GetEventLoop().IsInside());
|
||||
assert(context != nullptr);
|
||||
|
||||
const int which_events = nfs_which_events(context);
|
||||
|
||||
if (which_events == POLLOUT && SocketMonitor::IsDefined())
|
||||
/* kludge: if libnfs asks only for POLLOUT, it means
|
||||
that it is currently waiting for the connect() to
|
||||
finish - rpc_reconnect_requeue() may have been
|
||||
called from inside nfs_service(); we must now
|
||||
unregister the old socket and register the new one
|
||||
instead */
|
||||
SocketMonitor::Steal();
|
||||
|
||||
if (!SocketMonitor::IsDefined()) {
|
||||
int _fd = nfs_get_fd(context);
|
||||
if (_fd < 0)
|
||||
@@ -405,7 +416,8 @@ NfsConnection::ScheduleSocket()
|
||||
SocketMonitor::Open(_fd);
|
||||
}
|
||||
|
||||
SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
|
||||
SocketMonitor::Schedule(libnfs_to_events(which_events)
|
||||
| SocketMonitor::HANGUP);
|
||||
}
|
||||
|
||||
inline int
|
||||
@@ -442,10 +454,14 @@ NfsConnection::OnSocketReady(unsigned flags)
|
||||
bool closed = false;
|
||||
|
||||
const bool was_mounted = mount_finished;
|
||||
if (!mount_finished)
|
||||
if (!mount_finished || (flags & SocketMonitor::HANGUP) != 0)
|
||||
/* until the mount is finished, the NFS client may use
|
||||
various sockets, therefore we unregister and
|
||||
re-register it each time */
|
||||
/* also re-register the socket if we got a HANGUP,
|
||||
which is a sure sign that libnfs will close the
|
||||
socket, which can lead to a race condition if
|
||||
epoll_ctl() is called later */
|
||||
SocketMonitor::Steal();
|
||||
|
||||
const int result = Service(flags);
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include "output/OutputAPI.hxx"
|
||||
#include "event/MultiSocketMonitor.hxx"
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
@@ -53,6 +54,13 @@ public:
|
||||
DeferredMonitor::Schedule();
|
||||
}
|
||||
|
||||
~AlsaMixerMonitor() {
|
||||
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
||||
MultiSocketMonitor::Reset();
|
||||
DeferredMonitor::Cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void RunDeferred() override {
|
||||
InvalidateSockets();
|
||||
@@ -102,7 +110,7 @@ AlsaMixerMonitor::PrepareSockets()
|
||||
}
|
||||
|
||||
int count = snd_mixer_poll_descriptors_count(mixer);
|
||||
if (count < 0)
|
||||
if (count <= 0)
|
||||
count = 0;
|
||||
|
||||
struct pollfd *pfds = pfd_buffer.Get(count);
|
||||
|
@@ -24,8 +24,16 @@
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
/* work around a C++ incompatibility if the sndio API is emulated by
|
||||
libroar: libroar's "struct roar_service_stream" has a member named
|
||||
"new", which is an illegal identifier in C++ */
|
||||
#define new new_
|
||||
|
||||
#include <sndio.h>
|
||||
|
||||
/* undo the libroar workaround */
|
||||
#undef new
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#ifndef SIO_DEVANY
|
||||
|
@@ -60,7 +60,11 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url)
|
||||
{
|
||||
// The leading n is a placeholder for the length information
|
||||
auto icy_metadata = FormatString("nStreamTitle='%s';"
|
||||
"StreamUrl='%s';",
|
||||
"StreamUrl='%s';"
|
||||
/* pad 15 spaces just in case
|
||||
the length needs to be
|
||||
rounded up */
|
||||
" ",
|
||||
stream_title,
|
||||
stream_url);
|
||||
|
||||
@@ -68,7 +72,7 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url)
|
||||
|
||||
meta_length--; // subtract placeholder
|
||||
|
||||
meta_length = ((int)meta_length / 16) + 1;
|
||||
meta_length = meta_length / 16;
|
||||
|
||||
icy_metadata[0] = meta_length;
|
||||
|
||||
@@ -109,5 +113,5 @@ icy_server_metadata_page(const Tag &tag, const TagType *types)
|
||||
if (icy_string.IsNull())
|
||||
return nullptr;
|
||||
|
||||
return Page::Copy(icy_string.c_str(), (icy_string[0] * 16) + 1);
|
||||
return Page::Copy(icy_string.c_str(), uint8_t(icy_string[0]) * 16 + 1);
|
||||
}
|
||||
|
@@ -64,7 +64,10 @@ enum class SampleFormat : uint8_t {
|
||||
/**
|
||||
* Checks whether the sample format is valid.
|
||||
*/
|
||||
static constexpr inline bool
|
||||
#if !GCC_OLDER_THAN(5,0)
|
||||
constexpr
|
||||
#endif
|
||||
static inline bool
|
||||
audio_valid_sample_format(SampleFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
@@ -83,7 +86,10 @@ audio_valid_sample_format(SampleFormat format)
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr inline unsigned
|
||||
#if !GCC_OLDER_THAN(5,0)
|
||||
constexpr
|
||||
#endif
|
||||
static inline unsigned
|
||||
sample_format_size(SampleFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
|
@@ -44,7 +44,7 @@ add_tag_tag(TagType type, const char *value, void *ctx)
|
||||
unsigned n = strtoul(value, &end, 10);
|
||||
if (value != end) {
|
||||
char s[21];
|
||||
if (snprintf(s, 21, "%u", n) >= 0)
|
||||
if (snprintf(s, 21, "%u", n) > 0)
|
||||
tag.AddItem(type, s);
|
||||
}
|
||||
} else
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "TagTable.hxx"
|
||||
#include "TagBuilder.hxx"
|
||||
#include "util/Alloc.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -78,11 +79,9 @@ import_id3_string(const id3_ucs4_t *ucs4)
|
||||
if (gcc_unlikely(utf8 == nullptr))
|
||||
return nullptr;
|
||||
|
||||
id3_utf8_t *utf8_stripped = (id3_utf8_t *)
|
||||
xstrdup(Strip((char *)utf8));
|
||||
free(utf8);
|
||||
AtScopeExit(utf8) { free(utf8); };
|
||||
|
||||
return utf8_stripped;
|
||||
return (id3_utf8_t *)xstrdup(Strip((char *)utf8));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,9 +125,10 @@ tag_id3_import_text_frame(const struct id3_frame *frame,
|
||||
if (utf8 == nullptr)
|
||||
continue;
|
||||
|
||||
AtScopeExit(utf8) { free(utf8); };
|
||||
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
type, (const char *)utf8);
|
||||
free(utf8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +177,9 @@ tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type,
|
||||
if (utf8 == nullptr)
|
||||
return;
|
||||
|
||||
AtScopeExit(utf8) { free(utf8); };
|
||||
|
||||
tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
|
||||
free(utf8);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,22 +237,23 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
|
||||
if (name == nullptr)
|
||||
continue;
|
||||
|
||||
AtScopeExit(name) { free(name); };
|
||||
|
||||
id3_utf8_t *value = tag_id3_getstring(frame, 2);
|
||||
if (value == nullptr)
|
||||
continue;
|
||||
|
||||
AtScopeExit(value) { free(value); };
|
||||
|
||||
tag_handler_invoke_pair(handler, handler_ctx,
|
||||
(const char *)name,
|
||||
(const char *)value);
|
||||
|
||||
TagType type = tag_id3_parse_txxx_name((const char*)name);
|
||||
free(name);
|
||||
|
||||
if (type != TAG_NUM_OF_ITEM_TYPES)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
type, (const char*)value);
|
||||
|
||||
free(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -48,7 +48,7 @@ static size_t
|
||||
AlignToPageSize(size_t size)
|
||||
{
|
||||
static const long page_size = sysconf(_SC_PAGESIZE);
|
||||
if (page_size == 0)
|
||||
if (page_size <= 0)
|
||||
return size;
|
||||
|
||||
size_t ps(page_size);
|
||||
|
@@ -56,7 +56,7 @@ public:
|
||||
:buffer(std::exchange(src.buffer, nullptr)),
|
||||
capacity(std::exchange(src.capacity, 0)) {}
|
||||
|
||||
ReusableArray &operator=(const ReusableArray &&src) {
|
||||
ReusableArray &operator=(ReusableArray &&src) {
|
||||
std::swap(buffer, src.buffer);
|
||||
std::swap(capacity, src.capacity);
|
||||
return *this;
|
||||
|
@@ -60,6 +60,8 @@ ParseTimePoint(const char *s, const char *format)
|
||||
|
||||
#ifdef WIN32
|
||||
/* TODO: emulate strptime()? */
|
||||
(void)s;
|
||||
(void)format;
|
||||
throw std::runtime_error("Time parsing not implemented on Windows");
|
||||
#else
|
||||
struct tm tm;
|
||||
|
@@ -36,6 +36,7 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <new>
|
||||
#include <cstdlib>
|
||||
|
||||
/**
|
||||
* Allocate and construct a variable-size object. That is useful for
|
||||
|
@@ -5,11 +5,22 @@ import sys, subprocess
|
||||
|
||||
configure_args = sys.argv[1:]
|
||||
|
||||
host_arch = 'i686-w64-mingw32'
|
||||
x64 = True
|
||||
|
||||
if len(configure_args) > 0 and configure_args[0] == '--64':
|
||||
configure_args = configure_args[1:]
|
||||
while len(configure_args) > 0:
|
||||
arg = configure_args[0]
|
||||
if arg == '--64':
|
||||
x64 = True
|
||||
elif arg == '--32':
|
||||
x64 = False
|
||||
else:
|
||||
break
|
||||
configure_args.pop(0)
|
||||
|
||||
if x64:
|
||||
host_arch = 'x86_64-w64-mingw32'
|
||||
else:
|
||||
host_arch = 'i686-w64-mingw32'
|
||||
|
||||
# the path to the MPD sources
|
||||
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
|
||||
|
Reference in New Issue
Block a user