Compare commits

...

199 Commits

Author SHA1 Message Date
Max Kellermann
04ed50fb0f release v0.19.15 2016-04-30 14:21:52 +02:00
Max Kellermann
c9553411bb encoder/wave: add constant WAVE_FORMAT_PCM 2016-04-30 13:57:09 +02:00
Max Kellermann
62221adf55 encoder/wave: fix indent 2016-04-30 13:45:52 +02:00
Max Kellermann
a6bf4746c6 test/test_byte_reverse: use gcc_alignas() for gcc<4.8 compatibility 2016-04-30 13:41:24 +02:00
Michael Cree
72637d00e8 Bug#822848: mpd FTBFS on Alpha; misaligned arrays in the test suite
Source: mpd
Version: 0.19.14-2
Severity: important
Justification: fails to build form source (but built in the past)
Tags: patch
User: debian-alpha@lists.debian.org
Usertags: alpha

mpd FTBFS on Alpha with a failure in the test suite [1]:

FAIL: test/test_byte_reverse
============================

.F...

!!!FAILURES!!!
Test Results:
Run:  4   Failures: 1   Errors: 0

1) test: ByteReverseTest::TestByteReverse2 (F) line: 58 test/test_byte_reverse.cxx
assertion failed
- Expression: strcmp(result, (const char *)dest) == 0

This occurs because the test suite (in test/test_byte_reversal.cxx)
allocates static char arrays and passes the char arrays to functions
whose respective arguments were declared to be uint16_t *, etc., in
the main code.

This is in the realm of undefined behaviour on architectures with
strict memory alignment requirements.  Although the test only fails
on Alpha (because Alpha has a particular CPU load instruction that
gcc likes to use to add bugs ..., ahem,  optimise the code on the
assumption of alignment) it is potentially a latent bug for other
architectures with strict alignment requirements.

Since the code is compiled with the c++11 standard I attach a patch
that modifies the test suite to align the non-compliant strings with
the alignas() attribute.  The test suite now passes on Alpha with
that patch.

Cheers
Michael

[1] https://buildd.debian.org/status/fetch.php?pkg=mpd&arch=alpha&ver=0.19.14-2&stamp=1461542099
2016-04-28 13:29:41 +02:00
Florian Schlichting
27d4b15925 DecoderBuffer: add missing include
> In file included from src/decoder/DecoderBuffer.cxx:21:0:
> src/decoder/DecoderBuffer.hxx:41:20: error: 'uint8_t' was not declared in this scope
>   DynamicFifoBuffer<uint8_t> buffer;
>                     ^
> src/decoder/DecoderBuffer.hxx:41:27: error: template argument 1 is invalid
>   DynamicFifoBuffer<uint8_t> buffer;
>                            ^
> src/decoder/DecoderBuffer.hxx: In member function 'void DecoderBuffer::Clear()':
> src/decoder/DecoderBuffer.hxx:61:10: error: request for member 'Clear' in '((DecoderBuffer*)this)->DecoderBuffer::buffer', which is of non-class type 'int'
>    buffer.Clear();
>           ^
> src/decoder/DecoderBuffer.hxx: In member function 'size_t DecoderBuffer::GetAvailable() const':
> src/decoder/DecoderBuffer.hxx:78:17: error: request for member 'GetAvailable' in '((const DecoderBuffer*)this)->DecoderBuffer::buffer', which is of non-class type 'const int'
>    return buffer.GetAvailable();
>                  ^
> src/decoder/DecoderBuffer.hxx: In member function 'ConstBuffer<void> DecoderBuffer::Read() const':
> src/decoder/DecoderBuffer.hxx:87:19: error: request for member 'Read' in '((const DecoderBuffer*)this)->DecoderBuffer::buffer', which is of non-class type 'const int'
>    auto r = buffer.Read();
>                    ^
> src/decoder/DecoderBuffer.hxx:88:27: error: could not convert '{<expression error>, <expression error>}' from '<brace-enclosed initializer list>' to 'ConstBuffer<void>'
>    return { r.data, r.size };
>                            ^
> src/decoder/DecoderBuffer.hxx: In member function 'void DecoderBuffer::Consume(size_t)':
> src/decoder/DecoderBuffer.hxx:105:10: error: request for member 'Consume' in '((DecoderBuffer*)this)->DecoderBuffer::buffer', which is of non-class type 'int'
>    buffer.Consume(nbytes);
>           ^

This seems to be caused by a lacking include, fixed by the below patch.

I'm unsure what made this appear now, though, compiler and toolchain
libraries seem to be the same upstream versions that built 0.19.14-1
just fine in late March.
2016-04-25 08:30:27 +02:00
Max Kellermann
7a77767e66 doc/mpd.conf.5: move metadata_to_use to the user manual 2016-04-22 10:48:12 +02:00
Max Kellermann
1b26621860 doc/{user,protocol}: add a list of supported tags
A complete list which replaces the incomplete list in the mpd.conf
manpage.
2016-04-22 10:48:12 +02:00
Max Kellermann
3db5f4d0aa doc/mpd.conf.5: remove obsolete metadata_to_use sentence 2016-04-22 10:38:24 +02:00
Max Kellermann
b2a6e327bf doc: migrate to DocBook 4.5 2016-04-22 10:04:29 +02:00
Florian Schlichting
9aec5fe907 doc/user: fix typo 2016-04-22 09:25:17 +02:00
Max Kellermann
c731a82b71 decoder/opus: limit the number of packets in _scan_stream() 2016-04-19 13:08:07 +02:00
Max Kellermann
e6fad97edc decoder/opus: support bigger OpusTags packets
Required for OpusTags packets which contain artwork.

See https://bugs.musicpd.org/view.php?id=4520
2016-04-19 13:05:42 +02:00
Max Kellermann
70495aada1 decoder/ffmpeg: don't copy the AVPacket in ffmpeg_send_packet()
Reduce some overhead.  It is not necessary to copy the object.
2016-04-13 09:04:51 +02:00
Max Kellermann
f243f615ef decoder/ffmpeg: convert pointers to references 2016-04-13 09:01:54 +02:00
Max Kellermann
807c72b2f1 decoder/ffmpeg: use av_packet_unref() instead of av_free_packet()
av_free_packet() was deprecated in FFmpeg 3.0.
2016-04-12 21:15:05 +02:00
Max Kellermann
74dbaade6f decoder/Thread: use "ffmpeg" as fallback instead of "mad"
Adds support for stream codecs which havn't been explicitly listed in
ffmpeg_mime_types.
2016-03-30 00:58:48 +02:00
Max Kellermann
53677172f2 notify: use "constexpr" only with glibc
The Mutex and Cond constructors are only "constexpr" with glibc, and
this is what this #ifdef is about.

Backport of commit 459a812a

See http://bugs.musicpd.org/view.php?id=4511
2016-03-30 00:31:01 +02:00
Max Kellermann
bef0ccf42a configure.ac: prepare for 0.19.15 2016-03-30 00:30:39 +02:00
Max Kellermann
ff35aa07dc release v0.19.14 2016-03-18 18:26:58 +01:00
Max Kellermann
a3afd5178c tag/TagPool: optimize _dup_item()
When a reference counter is at its limit, don't allocate a new
TagPoolSlot - that would result in many TagPoolSlot instances with
ref==1.  This in turn would make the linked list very very large,
which means quadratic runtime for many operations.
2016-03-14 13:08:04 +01:00
Max Kellermann
f1285a6dfd tag/TagPool: add constexpr MAX_REF 2016-03-14 08:07:22 +01:00
Max Kellermann
cf7c1afb93 tag/TagPool: use prime number for NUM_SLOTS 2016-03-14 08:04:51 +01:00
Max Kellermann
e140a28073 archive/iso9660: check path buffer bounds 2016-03-07 14:21:01 +01:00
Max Kellermann
de61c3b962 archive/iso9660: use a single path buffer for Visit()
Avoid wasting 4 kB stack per directory level.
2016-03-07 14:01:52 +01:00
Max Kellermann
c46fc4531b archive/iso9660: move the "." and ".." checks up 2016-03-07 14:01:40 +01:00
Max Kellermann
065a9ed10f archive/iso9660: add local variable "filename" 2016-03-07 13:57:07 +01:00
Max Kellermann
e44c0254f7 archive/iso9660: make variables more local 2016-03-07 13:15:07 +01:00
Max Kellermann
13f9f0315f util/HugeAllocator: fix division by zero due to inverted check
There were two ways this could fail:

1. division by zero when sysconf(_SC_PAGESIZE)==0

2. mmap() failure because the size parameter is not aligned to page
   size

Neither ever happened: sysconf() never fails, and the only caller
passes a size that is already aligned.  Phew.
2016-03-06 23:53:41 +01:00
Max Kellermann
1532ffe215 protocol/ArgParser: fix range check
The old check

 unsigned(value) > std::numeric_limits<unsigned>::max()

.. cannot ever fail.
2016-03-06 23:41:08 +01:00
Max Kellermann
b24cbc68ba decoder/dsdiff: fix off-by-one buffer overflow 2016-03-06 23:28:29 +01:00
Max Kellermann
976fdd76c1 decoder/opus: limit tag size to 64 kB 2016-03-06 23:26:48 +01:00
Max Kellermann
bbda335e02 mixer/pulse: fix integer division rounding 2016-03-06 23:23:30 +01:00
Max Kellermann
d2dd6f7c70 thread/Posix{Mutex,Cond}: use "constexpr" only with glibc
Apparently all other C libraries are not compatible with "constexpr".
Those which are not will get a performance penalty, but at least they
work at all.
2016-03-01 21:23:59 +01:00
Max Kellermann
e9a544fa98 configure.ac: prepare for 0.19.14 2016-03-01 21:22:42 +01:00
Max Kellermann
79f2f8cddc release v0.19.13 2016-02-23 22:06:13 +01:00
Max Kellermann
39fa949345 queue/Playlist: move only the tag items in TagModified()
Fixes disappearing duration of remote songs during playback.

See http://bugs.musicpd.org/view.php?id=4492
2016-02-23 21:01:55 +01:00
Max Kellermann
e1d7a5cbf5 DetachedSong: add method MoveTagItemsFrom() 2016-02-23 20:59:44 +01:00
Max Kellermann
f3cefaf043 tag/Tag: move code to MoveItemsFrom() 2016-02-23 20:57:56 +01:00
Max Kellermann
b3460f3f54 configure.ac, unix/Daemon: check for initgroups() at configure time
The initgroups() manpage says we need to check for _BSD_SOURCE.  The
thing is that glibc deprecated this macro, and doesn't define it
anymore, effectively breaking all MPD supplementary groups.

The real fix is to check for initgroups() availability at configure
time, instead of relying on the deprecated _BSD_SOURCE macro.
2016-02-23 20:13:34 +01:00
Chris Spiegel
1e0ad1f6bf Add TAK as a supported FFmpeg format. 2016-02-19 17:32:48 +01:00
Max Kellermann
4abcb08cc9 tag/{aiff,riff}: fix ID3 chunk padding
Apply padding only to the fseek(), not to the chunk size.  This fixes
bogus "failed to read riff chunk" messages when the last chunk has an
odd size.

See http://bugs.musicpd.org/view.php?id=4486
2016-02-19 17:31:20 +01:00
Max Kellermann
81e7833711 configure.ac: prepare for 0.19.13 2016-02-19 17:30:01 +01:00
Max Kellermann
82e261ad33 release v0.19.12 2015-12-15 21:54:42 +01:00
Benno Fünfstück
cae2811762 fix mpd crash on invalid utf8 stream title 2015-12-15 21:49:53 +01:00
Ben Boeckel
09112c6869 docs: add vlc and mpv to the list of example applications
These are other popular clients. In particular, VLC is available on
mobile devices.

Signed-off-by: Ben Boeckel <mathstuf@gmail.com>
2015-12-15 21:30:26 +01:00
Christian Hesse
77aaf1baee fix LimitRTTIME in systemd unit file
systemd does not understand LimitRTTIME=-1. For no limit we have to use
the string 'infinity' (see systemd.exec(5)).

Signed-off-by: Christian Hesse <mail@eworm.de>
2015-12-15 21:17:04 +01:00
Jörg Krause
6626c2d00d Makefile.am: fix static build with alsa
Add ALSA_LIBS to MIXER_LIBS, otherwise building mpd in a static context fails
with lot of undefined references to alsa-lib (libasound) required by
src/mixer/plugins/AlsaMixerPlugin.cxx.

Signed-off-by: Jörg Krause <joerg.krause@embedded.rocks>
2015-12-15 21:16:45 +01:00
Michael Paquier
315f9d98f6 Main: fix build failure on non-Linux systems 2015-11-10 08:38:53 +01:00
Max Kellermann
f087518e7a configure.ac: prepare for 0.19.12 2015-11-10 08:33:50 +01:00
Max Kellermann
db9997a106 release v0.19.11 2015-10-27 10:42:20 +01:00
Max Kellermann
0cbfb610f2 systemd: remove obsolete ControlGroup settings
This systemd feature has been removed a while ago without replacement,
and it turns out that systemd developers suggest not using control
groups at all to assign real-time privileges.  Therfore, a replacement
feature will not be implement in future systemd releases, and we can
really remove those lines completely.

See http://bugs.musicpd.org/view.php?id=4413
2015-10-27 10:36:23 +01:00
Max Kellermann
f901cd042b doc/user: section about real-time scheduling 2015-10-27 10:31:50 +01:00
Max Kellermann
5719207dfa gme: don't loop forever, fall back to GME's default play length
Fixes http://bugs.musicpd.org/view.php?id=4432
2015-10-26 17:16:20 +01:00
Max Kellermann
a84fbbe327 decoder/gme: free the gme_info_t as early as possible 2015-10-26 17:15:24 +01:00
Max Kellermann
93c97972b9 decoder/gme: call decoder_seek_error() on seek error 2015-10-26 16:32:39 +01:00
Max Kellermann
ac61d43720 output/Command: flush the mixer cache when enabling/disabling output
Fixes mixer lag (http://bugs.musicpd.org/view.php?id=4425).
2015-10-26 16:29:07 +01:00
Max Kellermann
1958f78cc1 decoder/ffmpeg: fix crash due to wrong avio_alloc_context() call
Allocate the buffer dynamically using av_malloc(), and free
AVIOContext.buffer in the destructor, as mandated by the libavformat
documentation.

Fixes http://bugs.musicpd.org/view.php?id=4446
2015-10-26 13:06:29 +01:00
Max Kellermann
a7ee64a25b decoder/mpcdec: use SampleTraits<SampleFormat::S24_P32>
Eliminates some duplicate code, and as a side effect, this works
around clang 3.8 compiler warning because a negative value was
shifted.
2015-10-16 18:12:32 +02:00
Max Kellermann
2a58f22649 decoder/mpcdec: use Clamp() 2015-10-16 18:11:42 +02:00
Max Kellermann
f066bb7716 unix/Daemon, playlist/...: remove unused Domain variables 2015-10-16 18:08:59 +02:00
Max Kellermann
4e3d182189 encoder/flac: fix crash with 32 bit playback
Copy to encoder->audio_format *after* adjusting the sample format to
S24_P32.

Fixes http://bugs.musicpd.org/view.php?id=4433
2015-10-16 18:05:34 +02:00
Max Kellermann
205fba74cf tag/ApeLoader: fix buffer overflow after unterminated key 2015-10-16 14:55:40 +02:00
Max Kellermann
a9bcf8d50d configure.ac: prepare for 0.19.11 2015-10-16 14:55:40 +02:00
Max Kellermann
b0ff3bc7a3 release v0.19.10 2015-06-21 15:51:50 +02:00
Max Kellermann
06301e279c PlayerThread: start the decoder on PlayerCommand::QUEUE
Fixes missing SongBorder() call, which causes "single" mode breakage.
2015-06-21 15:38:48 +02:00
Max Kellermann
6d6f274648 DecoderAPI: discard unused song tag early
If there's a stream tag, don't let the song tag override it in the
next update_stream_tag() call.
2015-06-21 15:10:55 +02:00
Max Kellermann
9acefcb256 DecoderThread: set Decoder::song_tag only for local files
If the song tag comes from a stream, and MPD playback restarts, MPD
would believe the tag should override the newly received tag.  This
makes the previous tag appear stuck.  This change passes the song tag
only if it's authoritative - i.e. if it's a song file.
2015-06-21 15:02:14 +02:00
Max Kellermann
e4d0293a31 DecoderAPI: "move" the Tag object
Reduce runtime overhead.
2015-06-20 16:06:09 +02:00
Romain Rollet
ae77542a11 OutputControl: fix fail_timer check right after booting
Right after booting, the monotonic clock starts with a very small
value, and AudioOutput::LockUpdate() may believe that the fail_timer
has not recovered yet.
2015-06-20 15:37:19 +02:00
Max Kellermann
980187f856 system/PeriodClock: make IsDefined() "constexpr" 2015-06-20 15:33:17 +02:00
Max Kellermann
327a8e6c59 decoder/ffmpeg: skip unwanted samples after seeking
When seeking to the beginning of a packet, skip the samples that come
before the desired time stamp.
2015-06-20 15:01:05 +02:00
Max Kellermann
d11e2724c4 decoder/ffmpeg: use AVSEEK_FLAG_BACKWARD for seeking
Ask FFmpeg to seek to the next packet boundary *before* the seek
position, so we don't miss audio data.  Now we get too much, but we'll
solve that in the next commit.
2015-06-20 14:29:42 +02:00
Max Kellermann
f768ca3a2d decoder/ffmpeg: move code to StreamRelativePts() 2015-06-19 18:56:29 +02:00
Max Kellermann
947e902288 input/curl: trigger the condition variable in RequestDone()
Fixes deadlock on small responses.
2015-06-19 16:45:38 +02:00
Thomas Guillem
3436a646b5 storage/nfs: fix deadlock when connecting
The Connect method can be called between Schedule and lock. In that case, when
locked, the state is already set to CONNECTING of READY and the condition won't
be signaled anymore.
2015-05-29 22:39:14 +02:00
jai
aed0af1e00 input/smbclient: fix DFF playback 2015-05-29 22:37:49 +02:00
Michal Nazarewicz
0d7ee2b014 OpusEncoderPlugin: initialise granulepos so we end up with sane values
Not initialising granulepos leads to it having arbitrary values in the
encoded stream including possibly negative values which are not valid
and confuse opusdec.  Explicitly initialise opus_encoder::granulepos
to avoid that problem.
2015-03-25 18:02:54 +01:00
Max Kellermann
2f5fd91bd8 configure.ac: prepare for 0.19.10 2015-03-25 18:02:54 +01:00
Max Kellermann
5761800197 release v0.19.9 2015-02-06 17:08:25 +01:00
Max Kellermann
0eebacc521 Merge tag 'v0.18.23' into v0.19.x 2015-02-06 17:07:47 +01:00
Max Kellermann
4a5528697d release v0.18.23 2015-02-06 17:04:14 +01:00
Max Kellermann
d38034bb5c fs/io/FileOutputStream: don't auto-delete file on WIN32
The file handle is never reset to INVALID_HANDLE_VALUE, and thus the
destructor will assume the operation shall be cancelled and will
delete the temporary file.

This was a major breakage for saving the database file and the state
file.
2015-02-06 14:36:55 +01:00
Max Kellermann
b3fe3e8b3d TagBuilder: allow adding duplicate tag types in Complement()
Build a table of pre-existing tag types before adding new items.  The
old way would check HasType() each time, which would return true after
the first instance of that tag type had been added, preventing
duplicate tag types to be merged.

This broke duplicate tag types loaded from the state file, because
this code path uses TagBuilder::Complement().
2015-02-06 12:25:34 +01:00
Max Kellermann
5489dec28d NEWS: fix v0.18.22 release date 2015-02-01 12:22:24 +01:00
Max Kellermann
8a6b4db19f Makefile.am: move org_musicpd_Bridge.h to BUILT_SOURCES 2015-01-31 00:05:30 +01:00
Max Kellermann
df43b6a05c Makefile.am: generate icon before compiling JNI classes
Apparently, Android's build.xml requires the icon to be available,
even when only running the Java compiler.
2015-01-31 00:05:30 +01:00
Max Kellermann
3adca3c2fa db/update/Walk: use std::unique_ptr instead of std::auto_ptr
std::auto_ptr is deprecated, and std::unique_ptr is much better
anyway.
2015-01-29 08:37:23 +01:00
PHO
39abd3ecb4 Avoid integer overflow in MonotonicClock{S,MS,US}
This is Darwin specific: the previous implementation was causing an integer
overflow when base.numer is very large. On PPC Darwin, the timebase info is 1000000000/33330116 and this is too large for integer arithmetic.
2015-01-29 08:34:37 +01:00
PHO
a4f4fc50b9 Avoid integer overflow in MonotonicClock{S,MS,US}
This is Darwin specific: the previous implementation was causing an integer
overflow when base.numer is very large. On PPC Darwin, the timebase info is 1000000000/33330116 and this is too large for integer arithmetic.
2015-01-29 08:33:48 +01:00
Max Kellermann
7bf638b0de decoder/DsdLib: use new[] to allocate the ID3 buffer
Don't abort the process if there's not enough memory.  This buffer is
not important and can be large.
2015-01-29 08:24:34 +01:00
Max Kellermann
56662a703c decoder/DsdLib: free ID3 buffer right after id3_tag_parse()
Merge two free() calls.
2015-01-29 08:24:34 +01:00
Max Kellermann
8b5f47d3a3 decoder/DsdLib: raise ID3 tag limit to 1 MB
A bug report was submitted with a 600 kB ID3 tag that could not be
read by MPD.
2015-01-29 08:20:14 +01:00
Max Kellermann
a289dcb9ee Merge branch 'v0.18.x' into v0.19.x 2015-01-26 20:48:19 +01:00
PHO
023b9c1e7e Test the existence of strndup(3) before using it.
This can eliminate the ad-hoc "#ifdef WIN32" and can also support other platforms lacking it as well (including Darwin 9).
2015-01-26 20:39:49 +01:00
Max Kellermann
4c61662644 test/read_mixer: add missing stdlib.h include 2015-01-26 20:39:49 +01:00
Max Kellermann
ad1b6ef0ac {playlist,input}/despotify: remove defunct plugin 2015-01-26 09:55:31 +01:00
Max Kellermann
ed5c6be2f1 util/list: disable gcc5 warning
This file has been removed in newer MPD versions, so don't care about
it now.
2015-01-23 16:50:31 +01:00
Max Kellermann
30cb082932 ClientProcess: cast enum to int before passing to printf()
Fixes gcc5 warning.
2015-01-23 16:50:31 +01:00
Max Kellermann
645554d12f configure.ac: prepare for 0.18.23 2015-01-23 16:47:13 +01:00
Max Kellermann
212b0faf0c android/build.py: use os.path.abspath() to build mpd_path
Fixes a bug that occurs when runing "build.py" from inside the
"android" directory.
2015-01-22 18:51:53 +01:00
Max Kellermann
276a0d9500 thread/Name: include stdio.h if HAVE_PRCTL
Caused a build failure with uClibc because snprintf() was not
available.
2015-01-21 20:40:56 +01:00
Max Kellermann
384b6c8288 doc/protocol: "playlistsearch" is case insensitive 2015-01-16 17:27:19 +01:00
Max Kellermann
a2af158fd3 configure.ac: prepare for 0.19.9 2015-01-16 17:26:50 +01:00
Max Kellermann
f33d2fb2e7 release v0.19.8 2015-01-14 23:12:24 +01:00
Max Kellermann
a9eec35aff Merge tag 'v0.18.22' into v0.19.x 2015-01-14 23:12:08 +01:00
Max Kellermann
8534f2d1e2 release v0.18.22 2015-01-14 23:04:49 +01:00
Max Kellermann
00740fb23b android/build.py: prepend "./" to "configure" if path is empty
Fixes in-tree build when the script is called as "android/build.py"
and not "./android/build.py".
2015-01-09 16:51:52 +01:00
Max Kellermann
37e9010887 input/async: reset the "open" flag after seeking successfully
Fixes a problem with the "curl" input plugin: IsEOF() always returns
true because the "open" flag was cleared by
CurlInputStream::RequestDone() when end-of-stream was reached.  This
flag stays false even when seeking to another position has succeeded.

This patch resets the "open" flag to true after seeking successfully.
2015-01-06 12:46:28 +01:00
Max Kellermann
4bd2c75056 thread/Name: disable pthread_setname_np() on NetBSD
NetBSD's pthread_setname_np() prototype is incompatible with the rest
of the world, and it requires to pass the string argument as a
non-const pointer.  Instead of working around this misdesign, I hereby
disable the feature on NetBSD.
2015-01-06 12:08:36 +01:00
Max Kellermann
b9ed850b98 thread/Name: enable FormatThreadName() with prctl()
Add macro HAVE_THREAD_NAME which is set when any method to set the
thread name is available.  Use that macro in FormatThreadName()
instead of just checking for HAVE_PTHREAD_SETNAME_NP.
2015-01-06 12:04:30 +01:00
Max Kellermann
11cea17496 thread/Name: indent preprocessor commands 2015-01-06 12:04:15 +01:00
Max Kellermann
163597ef69 db/simple: fix implicit nullptr/bool conversion
Return false on error, not nullptr.
2014-12-26 14:34:03 +01:00
Max Kellermann
95f84afd33 fs/Traits, ...: work around -Wtautological-pointer-compare
New in clang 3.6.
2014-12-26 14:34:03 +01:00
Max Kellermann
9f7fd1fbfb db/lazy, input/mms: add "override" keywords
Fixes -Winconsistent-missing-override (clang 3.6).
2014-12-26 14:29:29 +01:00
Max Kellermann
940cab8620 Merge branch 'v0.18.x' into v0.19.x 2014-12-26 14:28:52 +01:00
Max Kellermann
5b84c99d79 doc/user: remove autoconf/automake from Debian build dependencies 2014-12-26 14:00:50 +01:00
Max Kellermann
b295024574 doc/user: add more Debian build dependencies 2014-12-26 13:56:26 +01:00
Max Kellermann
34180f1745 doc/user: add libicu-dev to Debian build dependencies 2014-12-26 13:54:33 +01:00
Max Kellermann
665031467a db/proxy, output/shout: fix implicit nullptr/bool conversion
Return false on error, not nullptr.
2014-12-26 13:50:54 +01:00
Max Kellermann
df33171107 db/{simple,proxy}, ...: add "override" keywords
Fixes -Winconsistent-missing-override (clang 3.6).
2014-12-26 13:47:04 +01:00
Max Kellermann
53f4044890 util/{ASCII,UriUtil}, ...: work around -Wtautological-pointer-compare
New in clang 3.6.
2014-12-26 13:43:32 +01:00
Max Kellermann
a5049136ff DatabaseGlue: convert nullptr check to assertion 2014-12-26 13:43:32 +01:00
Max Kellermann
705b3c6b63 util/ASCII: fix indent 2014-12-26 13:37:38 +01:00
Max Kellermann
6b4ac66962 Compiler.h: add macro CLANG_CHECK_VERSION() 2014-12-26 13:31:03 +01:00
Max Kellermann
0964b06240 Compiler.h: add macro GCC_OLDER_THAN() 2014-12-26 13:30:44 +01:00
Max Kellermann
92eeca3ba7 util/Manual: reimplement GCC_CHECK_VERSION() using GCC_MAKE_VERSION() 2014-12-26 13:30:22 +01:00
Max Kellermann
2a86554ac4 Compiler.h: add macro GCC_MAKE_VERSION() 2014-12-26 13:30:11 +01:00
Max Kellermann
805caa30ce configure.ac: prepare for 0.18.22 2014-12-26 13:23:04 +01:00
Max Kellermann
a56949e9fa decoder/ffmpeg: support interleaved floating point 2014-12-23 20:51:08 +01:00
Max Kellermann
43da4c0eca input/mms: limit the mmsx_read() size 2014-12-23 20:34:45 +01:00
Max Kellermann
b9c7771830 decoder/DsdLib: add missing stdlib.h include 2014-12-23 10:08:46 +01:00
Jan Brittenson
35db88affe DSF ID3 tags hitting 4k size limit
Here's a change to dynamically allocate the DSD ID3 tag buffer.
Pretty much anything with cover art is going to exceed the existing,
static 4k limit...  Here's a change to dynamically allocate the buffer
and sanity check it at some upper limit.  I rather arbitrarily pulled
256k out of thin air just to keep a corrupt file from causing it to
trying to allocate a buffer larger than available memory.
2014-12-23 09:49:33 +01:00
Max Kellermann
e38faca455 configure.ac: prepare for 0.19.8 2014-12-23 09:48:31 +01:00
Max Kellermann
0255e8710c android: release v0.19.7 2014-12-23 09:42:52 +01:00
Max Kellermann
6d89020f80 release v0.19.7 2014-12-17 19:20:54 +01:00
Max Kellermann
9c56c49e73 Merge tag 'v0.18.21' into v0.19.x 2014-12-17 19:19:13 +01:00
Max Kellermann
acb798e544 release v0.18.21 2014-12-17 19:13:47 +01:00
Max Kellermann
c5720a15c7 LogBackend: force-flush stderr on WIN32
setvbuf() does not seem to have an effect on Windows.
2014-12-17 19:12:25 +01:00
Max Kellermann
90709b332a LogInit: make stderr line-buffered
Make sure everything gets logged right away.  No delays because
stdio's buffer is not yet full.
2014-12-17 19:12:01 +01:00
Max Kellermann
81f17d10c8 util/HugeAllocator: enable MEM_COMMIT on Windows
Without MEM_COMMIT, the reserved address space is not accessible, and
MPD crashes.
2014-12-17 19:10:58 +01:00
k44
773de38bd9 playlist/embcue: fix filename suffix detection
The definition of the playlist_plugin struct member of the embcue
plugin was incorrect.
2014-12-16 18:43:05 +01:00
Max Kellermann
a48704925d storage/nfs: add timeout 2014-12-15 00:45:13 +01:00
Max Kellermann
fa4beeee75 decoder/ffmpeg: detect and fix negative time stamps
Works around assertion failure due to something that appears to be a
(minor) FFmpeg bug.
2014-12-15 00:40:46 +01:00
Max Kellermann
d8351772d3 configure.ac: prepare for 0.18.21 2014-12-15 00:39:52 +01:00
Max Kellermann
68d1abdb85 storage/nfs: clear last_error in SetState()
Fixes bogus assertion failure.
2014-12-15 00:39:30 +01:00
Max Kellermann
7e8474a85a lib/nfs/Connection: unregister socket with SocketMonitor::Steal()
SocketMonitor::Cancel() does not actually unregister the socket; it
only disables the event.
2014-12-15 00:31:12 +01:00
Max Kellermann
82da364b8b lib/nfs/Connection: implement mount timeout 2014-12-15 00:05:53 +01:00
Max Kellermann
7fa91ec175 lib/nfs/Connection: add debug flag "in_destroy" 2014-12-15 00:03:30 +01:00
Max Kellermann
1d3a09d377 lib/nfs/Connection: add assertion 2014-12-14 22:51:37 +01:00
Max Kellermann
02563a35f0 lib/nfs/Connection: fix reconnect after mount failure
When mounting had not yet finished, SocketMonitor::IsDefined() was
always false, due to the workaround at the beginning of the function
that calls SocketMonitor::Steal().  This commit drops the IsDefined()
check because it was never necessary and breaks reconnect.
2014-12-14 22:49:16 +01:00
Max Kellermann
d653f35bb7 lib/nfs/Connection: fix typo in code comment 2014-12-14 22:49:09 +01:00
Max Kellermann
a543627abd lib/nfs/Connection: fix memory leak (and assertion failure)
nfs_destroy_context() will invoke all pending callbacks with
err==-EINTR.  In CancellableCallback::Callback(), this will invoke
NfsConnection::DeferClose(), which however is only designed to be
called from nfs_service().  In non-debug mode, this will leak memory
because nfs_close_async() is never called.

Workaround: before nfs_destroy_context(), invoke nfs_close_async() on
all pending file handles.
2014-12-14 16:02:47 +01:00
Max Kellermann
80f2ba7fca lib/nfs/Connection: move code to Service() 2014-12-14 15:45:10 +01:00
Max Kellermann
32bca64920 lib/nfs/Connection: add assertions 2014-12-14 15:40:29 +01:00
Max Kellermann
7fa1a84ec3 lib/nfs/Connection: move code to method InternalClose() 2014-12-14 15:38:09 +01:00
Max Kellermann
ab4bb26a0a lib/nfs/Connection: make in_service and in_event debug-only flags 2014-12-14 15:20:40 +01:00
Max Kellermann
4b8d258cff lib/nfs/Connection: fix crash while canceling a failing Open()
The method NfsConnection::CancellableCallback::Callback() will always
invoke NfsConnection::Close() on the file handle, even if the void
pointer is not a nfsfh.  This can happen if the Open() was not
successful, e.g. when the file does not exist.
2014-12-14 15:16:01 +01:00
Max Kellermann
3c29aa6271 event/Loop: read the "again" flag while holding mutex 2014-12-14 14:47:36 +01:00
Max Kellermann
51464b4317 lib/nfs/Connection: add assertions 2014-12-14 14:24:49 +01:00
Max Kellermann
2fec463542 util/HugeAllocator: disable MEM_LARGE_PAGES on Windows
MEM_LARGE_PAGES does not appear to work.  Instead, MEM_RESERVE appears
to be necessary.  Until I figure this out, this large pages are
disabled.
2014-12-12 13:20:58 +01:00
Max Kellermann
1affc641c4 input/Init: eliminate double colon from log message 2014-12-12 13:20:37 +01:00
Max Kellermann
0cfd4fff62 playlist/Print: don't skip non-existent songs in "listplaylist"
Skipping those songs silently will confuse the client, because
commands specifying the song index within a playlist
(e.g. playlistdelete) will be out of sync.

This copies spl_print()'s behavior to playlist_file_print().
2014-12-09 13:36:48 +01:00
Max Kellermann
8904127c10 configure.ac: prepare for 0.19.7 2014-12-09 13:09:03 +01:00
Max Kellermann
c46f48abec release v0.19.6 2014-12-08 15:12:41 +01:00
Max Kellermann
4acbf7b90d android/build.py: update FFmpeg to 2.5 2014-12-08 15:05:49 +01:00
Max Kellermann
cbc1a58e93 Merge tag 'v0.18.20' into v0.19.x 2014-12-08 15:03:09 +01:00
Max Kellermann
1b5f33a435 release v0.18.20 2014-12-08 14:57:17 +01:00
Max Kellermann
41b4a63f2b decoder/ffmpeg: support FFmpeg 2.5
Version 2.5 fixed an API oddity, however it broke API compatibility,
at least with C++.  Disable the workaround when a libavformat version
is detected that is recent enough.
2014-12-08 14:25:34 +01:00
Max Kellermann
d8fc2db910 thread/Id: drop "::" prefix before pthread function names
The "::" to explicitly refer to the global namespace appeared like a
good idea in C++, but it breaks with C libraries that implement
standard functions using macros (e.g. musl).
2014-12-08 14:17:17 +01:00
Max Kellermann
dc11dea7cc configure.ac: prepare for 0.18.20 2014-12-08 14:13:20 +01:00
Nix
811af02f56 Output: start with a null mixer.
There are code paths (mostly error cases) in which it is possible to
initialize an AudioOutput and then kill it without ever calling
audio_output_new().  In such a case, its destructor will attempt to
free a mixer that was never initialized, leading to an attempt to
take out a lock on a mutex that was similarly never initialized,
which hangs forever.

Fix by always initializing the mixer appropriately.
2014-12-01 22:14:09 +01:00
Max Kellermann
8780e23ed3 android/build.py: update ffmpeg 2.4.3, curl 7.39 2014-11-28 21:08:27 +01:00
Max Kellermann
be492ed108 android: update libFLAC to 1.3.1
Due to security vulnerabilities.
2014-11-28 21:08:27 +01:00
Max Kellermann
24da14f4f7 .gitignore: ignore /lib/ 2014-11-28 21:08:18 +01:00
Max Kellermann
03d2fb450f configure.ac: prepare for 0.19.6 2014-11-28 20:13:57 +01:00
Max Kellermann
67cba251c8 release v0.19.5 2014-11-26 20:11:42 +01:00
Max Kellermann
0bc511715b Makefile.am: distribute Android sources 2014-11-26 20:11:42 +01:00
Max Kellermann
27ce80544f Merge tag 'v0.18.19' into v0.19.x 2014-11-26 20:02:57 +01:00
Max Kellermann
04f627c2af release v0.18.19 2014-11-26 19:58:48 +01:00
Max Kellermann
e72eef421b lib/nfs/FileReader: clean up on disconnect
Avoids crash because Close() invokes a call on a destructed
NfsConnection.
2014-11-25 14:02:15 +01:00
Max Kellermann
016063c810 lib/nfs/FileReader: move code to CancelOrClose() 2014-11-25 14:00:32 +01:00
Max Kellermann
38f19981b2 lib/nfs/FileReader: reset state in OnNfsConnectionFailed()
Avoid calling NfsConnection::RemoveLease(), because the lease has been
removed already.
2014-11-25 13:51:09 +01:00
Max Kellermann
40dd968f13 lib/nfs/FileReader: update "state" in OnNfsError()
Clean up the "state" to indicate that there is no longer any
asynchronous operation.  Fixes another NFS-related crash due to
cleanup of a non-existing asynchronous operation.
2014-11-25 13:39:42 +01:00
Max Kellermann
3cef348f30 lib/nfs/Manager: defer NfsConnection destruction
Avoids a crash that occurs when NfsConnection::OnSocketReady()
dereferences itself before returning.
2014-11-25 13:31:18 +01:00
Max Kellermann
b293b16007 lib/nfs/Connection: broadcast error before closing connection
During the NfsLease::OnNfsConnectionFailed() call, the old (defunct)
nfs_context may be used to close file handles.  Such code does not yet
exist, but will be added soon to fix other bugs.
2014-11-25 13:27:06 +01:00
Max Kellermann
f5f43db2da lib/nfs/Connection: cancel DeferredMonitor on disconnect
Fixes potential second mount attempt after the old connection to the
NFS server was shut down.
2014-11-25 13:22:25 +01:00
Max Kellermann
029555d192 lib/nfs/FileReader: include Compiler.h for "final" fallback 2014-11-25 13:18:22 +01:00
Max Kellermann
fa4d202e71 decoder/mp4v2: remove because of incompatible license
libmp4v2 is licensed under MPL 1.1, which is incompatible with GPLv2.
Unfortunately, this means that we must remove the plugin.

More information can be found in the Debian bug report:

 http://bugs.debian.org/767504
2014-11-25 13:10:52 +01:00
Max Kellermann
a8ebfd7a92 event/DeferredMonitor: include cleanup 2014-11-25 10:44:06 +01:00
Max Kellermann
b19e5720cc test/run_input: make variables more local 2014-11-25 07:51:33 +01:00
Max Kellermann
a254f5a3a8 archive/zzip: fix inverted error handler
Set the Error when zzip_seek()==-1 and not on success.  Fixes a crash
after seeking.
2014-11-24 22:08:50 +01:00
Max Kellermann
143c735f96 configure.ac: prepare for 0.18.19 2014-11-24 22:08:50 +01:00
Max Kellermann
951bad46e0 decoder/{dsdiff,dsf,opus}: fix deadlock while seeking 2014-11-24 08:54:30 +01:00
Max Kellermann
716225cd2f doc/protocol: mention that "count" can have multiple filters 2014-11-24 08:09:58 +01:00
Max Kellermann
bbc618b8f9 configure.ac: prepare for 0.19.5 2014-11-24 08:09:44 +01:00
Max Kellermann
11ead56d6d android: release v0.19.4
Android releases were missing since 0.19.1.
2014-11-24 08:00:47 +01:00
Max Kellermann
e972ae4afa android: switch to gcc 4.9 / llvm 3.5 (NDK r10c) 2014-11-24 08:00:45 +01:00
Max Kellermann
0709065f50 Java/File: fix include guard 2014-11-24 07:51:25 +01:00
126 changed files with 1312 additions and 1626 deletions
.gitignoreINSTALLMakefile.amNEWS
android
configure.ac
doc
src
Compiler.hDetachedSong.hxxIdle.cxxLogBackend.cxxLogInit.cxxMain.cxxPlayerThread.cxxSongFilter.cxxSongLoader.cxx
archive
client
db
decoder
encoder
event
filter
fs
input
java
lib
ls.cxx
mixer
notify.hxx
output
pcm
playlist
protocol
queue
storage
system
tag
thread
unix
util
systemd
test

2
.gitignore vendored

@@ -77,6 +77,8 @@ tags
/test/test_vorbis_encoder
/test/DumpDatabase
/lib/
/*.tar.gz
/*.tar.bz2
/*.tar.xz

@@ -116,12 +116,6 @@ For WavPack playback.
libadplug - http://adplug.sourceforge.net/
For AdLib playback.
despotify - https://github.com/SimonKagstrom/despotify
For Spotify playback.
MP4v2 - https://code.google.com/p/mp4v2/
For MP4 playback. You will need FAAD2.
Optional Miscellaneous Dependencies
-----------------------------------

@@ -28,8 +28,6 @@ noinst_LIBRARIES = \
libmixer_plugins.a \
liboutput_plugins.a
libmpd_a_DEPENDENCIES =
libmpd_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(LIBMPDCLIENT_CFLAGS) \
$(AVAHI_CFLAGS) \
@@ -283,13 +281,13 @@ android/build/build.xml: android/AndroidManifest.xml
ln -s $(abs_srcdir)/android/res/values android/build/res
$(ANDROID_SDK)/tools/android update project --path android/build --target android-17
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml android/build/res/drawable/icon.png
cd android/build && ant compile-jni-classes
android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
libmpd_a_DEPENDENCIES += android/build/include/org_musicpd_Bridge.h
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
mkdir -p $(@D)
@@ -828,7 +826,6 @@ libdecoder_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(WAVPACK_CFLAGS) \
$(MAD_CFLAGS) \
$(MPG123_CFLAGS) \
$(MP4V2_CFLAGS) \
$(OPUS_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(MPCDEC_CFLAGS) \
@@ -848,7 +845,6 @@ DECODER_LIBS = \
$(WAVPACK_LIBS) \
$(MAD_LIBS) \
$(MPG123_LIBS) \
$(MP4V2_LIBS) \
$(OPUS_LIBS) \
$(FFMPEG_LIBS2) \
$(MPCDEC_LIBS) \
@@ -963,12 +959,6 @@ noinst_LIBRARIES += libmodplug_decoder_plugin.a
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
endif
if HAVE_MP4V2
libdecoder_a_SOURCES += \
src/decoder/plugins/Mp4v2DecoderPlugin.cxx \
src/decoder/plugins/Mp4v2DecoderPlugin.hxx
endif
if ENABLE_SIDPLAY
libdecoder_a_SOURCES += \
src/decoder/plugins/SidplayDecoderPlugin.cxx \
@@ -1133,7 +1123,6 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(NFS_CFLAGS) \
$(CDIO_PARANOIA_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(DESPOTIFY_CFLAGS) \
$(MMS_CFLAGS)
INPUT_LIBS = \
@@ -1143,7 +1132,6 @@ INPUT_LIBS = \
$(NFS_LIBS) \
$(CDIO_PARANOIA_LIBS) \
$(FFMPEG_LIBS2) \
$(DESPOTIFY_LIBS) \
$(MMS_LIBS)
if HAVE_ALSA
@@ -1189,15 +1177,6 @@ libinput_a_SOURCES += \
src/input/plugins/MmsInputPlugin.cxx src/input/plugins/MmsInputPlugin.hxx
endif
if ENABLE_DESPOTIFY
libinput_a_SOURCES += \
src/lib/despotify/DespotifyUtils.cxx \
src/lib/despotify/DespotifyUtils.hxx \
src/input/plugins/DespotifyInputPlugin.cxx \
src/input/plugins/DespotifyInputPlugin.hxx
endif
liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
@@ -1240,6 +1219,7 @@ liboutput_plugins_a_SOURCES = \
MIXER_LIBS = \
libmixer_plugins.a \
$(ALSA_LIBS) \
$(PULSE_LIBS)
MIXER_API_SRC = \
@@ -1402,14 +1382,6 @@ PLAYLIST_LIBS = \
$(EXPAT_LIBS) \
$(FLAC_LIBS)
if ENABLE_DESPOTIFY
libplaylist_plugins_a_SOURCES += \
src/lib/despotify/DespotifyUtils.cxx \
src/lib/despotify/DespotifyUtils.hxx \
src/playlist/plugins/DespotifyPlaylistPlugin.cxx \
src/playlist/plugins/DespotifyPlaylistPlugin.hxx
endif
if ENABLE_SOUNDCLOUD
libplaylist_plugins_a_SOURCES += \
src/playlist/plugins/SoundCloudPlaylistPlugin.cxx \
@@ -1646,12 +1618,6 @@ if HAVE_LIBUPNP
test_run_neighbor_explorer_SOURCES += src/lib/expat/ExpatParser.cxx
endif
if ENABLE_DESPOTIFY
test_run_neighbor_explorer_SOURCES += \
src/lib/despotify/DespotifyUtils.cxx \
src/lib/despotify/DespotifyUtils.hxx
endif
endif
if ENABLE_ARCHIVE
@@ -2143,7 +2109,9 @@ developer_DATA = $(wildcard doc/developer/*.html)
DOCBOOK_HTML = $(patsubst %.xml,%/index.html,$(DOCBOOK_FILES))
$(DOCBOOK_HTML): %/index.html: %.xml
DOCBOOK_INCLUDES = $(wildcard $(srcdir)/doc/include/*.xml)
$(DOCBOOK_HTML): %/index.html: %.xml $(DOCBOOK_INCLUDES)
$(XMLTO) -o $(@D) --stringparam chunker.output.encoding=utf-8 html --stringparam use.id.as.filename=1 $<
doc/api/html/index.html: doc/doxygen.conf
@@ -2187,4 +2155,11 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
$(wildcard scripts/*.sh) \
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
systemd/mpd.socket \
android/AndroidManifest.xml \
android/build.py \
android/custom_rules.xml \
android/res/values/strings.xml \
android/src/Bridge.java \
android/src/Loader.java \
android/src/Main.java \
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico

134
NEWS

@@ -1,3 +1,114 @@
ver 0.19.15 (2016/04/30)
* decoder
- ffmpeg: support FFmpeg 3.0
- ffmpeg: use as fallback instead of "mad" if no plugin matches
- opus: support bigger OpusTags packets
* fix more build failures on non-glibc builds due to constexpr Mutex
* fix build failure due to missing include
* fix unit test on Alpha
ver 0.19.14 (2016/03/18)
* decoder
- dsdiff: fix off-by-one buffer overflow
- opus: limit tag size to 64 kB
* archive
- iso9660: fix buffer overflow
* fix quadratic runtime bug in the tag pool
* fix build failures on non-glibc builds due to constexpr Mutex
ver 0.19.13 (2016/02/23)
* tags
- aiff, riff: fix ID3 chunk padding
* decoder
- ffmpeg: support the TAK codec
* fix disappearing duration of remote songs during playback
* initialize supplementary groups with glibc 2.19+
ver 0.19.12 (2015/12/15)
* fix assertion failure on malformed UTF-8 tag
* fix build failure on non-Linux systems
* fix LimitRTTIME in systemd unit file
ver 0.19.11 (2015/10/27)
* tags
- ape: fix buffer overflow
* decoder
- ffmpeg: fix crash due to wrong avio_alloc_context() call
- gme: don't loop forever, fall back to GME's default play length
* encoder
- flac: fix crash with 32 bit playback
* mixer
- fix mixer lag after enabling/disabling output
ver 0.19.10 (2015/06/21)
* input
- curl: fix deadlock on small responses
- smbclient: fix DFF playback
* decoder
- ffmpeg: improve seeking accuracy
- fix stuck stream tags
* encoder
- opus: fix bogus granulepos
* output
- fix failure to open device right after booting
* neighbor
- nfs: fix deadlock when connecting
* fix "single" mode breakage due to queue edits
ver 0.19.9 (2015/02/06)
* decoder
- dsdiff, dsf: raise ID3 tag limit to 1 MB
* playlist: fix loading duplicate tag types from state file
* despotify: remove defunct plugin
* fix clock integer overflow on OS X
* fix gcc 5.0 warnings
* fix build failure with uClibc
* fix build failure on non-POSIX operating systems
* fix dependency issue on parallel Android build
* fix database/state file saving on Windows
ver 0.19.8 (2015/01/14)
* input
- curl: fix bug after rewinding from end-of-file
- mms: reduce delay at the beginning of playback
* decoder
- dsdiff, dsf: allow ID3 tags larger than 4 kB
- ffmpeg: support interleaved floating point
* fix clang 3.6 warnings
* fix build failure on NetBSD
ver 0.19.7 (2014/12/17)
* input
- nfs: fix crash while canceling a failing file open operation
- nfs: fix memory leak on connection failure
- nfs: fix reconnect after mount failure
- nfs: implement mount timeout (60 seconds)
* storage
- nfs: implement I/O timeout (60 seconds)
* playlist
- embcue: fix filename suffix detection
- don't skip non-existent songs in "listplaylist"
* decoder
- ffmpeg: fix time stamp underflow
* fix memory allocator bug on Windows
ver 0.19.6 (2014/12/08)
* decoder
- ffmpeg: support FFmpeg 2.5
* fix build failure with musl
* android
- update libFLAC to 1.3.1
- update FFmpeg to 2.5
ver 0.19.5 (2014/11/26)
* input
- nfs: fix crash on connection failure
* archive
- zzip: fix crash after seeking
* decoder
- dsdiff, dsf, opus: fix deadlock while seeking
- mp4v2: remove because of incompatible license
ver 0.19.4 (2014/11/18)
* protocol
- workaround for buggy clients that send "add /"
@@ -129,6 +240,29 @@ ver 0.19 (2014/10/10)
* install systemd unit for socket activation
* Android port
ver 0.18.23 (2015/02/06)
* despotify: remove defunct plugin
* fix clock integer overflow on OS X
* fix gcc 5.0 warnings
ver 0.18.22 (2015/01/14)
* fix clang 3.6 warnings
ver 0.18.21 (2014/12/17)
* playlist
- embcue: fix filename suffix detection
* decoder
- ffmpeg: fix time stamp underflow
ver 0.18.20 (2014/12/08)
* decoder
- ffmpeg: support FFmpeg 2.5
* fix build failure with musl
ver 0.18.19 (2014/11/26)
* archive
- zzip: fix crash after seeking
ver 0.18.18 (2014/11/18)
* decoder
- ffmpeg: support opus

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="7"
android:versionName="0.19.1">
android:versionCode="13"
android:versionName="0.19.9">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>

@@ -23,7 +23,7 @@ if not os.path.isdir(ndk_path):
sys.exit(1)
# the path to the MPD sources
mpd_path = os.path.dirname(os.path.dirname(sys.argv[0]))
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
# output directories
lib_path = os.path.abspath('lib')
@@ -40,8 +40,8 @@ build_arch = 'linux-x86_64'
os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(root_path, 'lib/pkgconfig')
# select the NDK compiler
gcc_version = '4.8'
llvm_version = '3.3'
gcc_version = '4.9'
llvm_version = '3.5'
# select the NDK target
ndk_arch = 'arm'
@@ -314,8 +314,8 @@ thirdparty_libs = [
),
AutotoolsProject(
'https://svn.xiph.org/releases/flac/flac-1.3.0.tar.xz',
'13b5c214cee8373464d3d65dee362cdd',
'http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz',
'b9922c9a0378c88d3e901b234f852698',
'lib/libFLAC.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ thirdparty_libs = [
),
FfmpegProject(
'http://www.ffmpeg.org/releases/ffmpeg-2.2.3.tar.bz2',
'dbb5b6b69bd010916f17df0ae596e0b1',
'http://ffmpeg.org/releases/ffmpeg-2.5.tar.bz2',
'4346fe710cc6bdd981f6534d2420d1ab',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -366,8 +366,8 @@ thirdparty_libs = [
),
AutotoolsProject(
'http://curl.haxx.se/download/curl-7.37.0.tar.lzma',
'54bfd1eb5214f604186d6f5ac61c7781',
'http://curl.haxx.se/download/curl-7.39.0.tar.lzma',
'e9aa6dec29920eba8ef706ea5823bad7',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',

@@ -1,10 +1,10 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.19.4, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.19.15, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=19
VERSION_REVISION=4
VERSION_REVISION=15
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
@@ -206,6 +206,8 @@ if test x$host_is_linux = xyes; then
fi
AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
AC_CHECK_FUNCS(initgroups)
AC_CHECK_FUNCS(strndup)
if test x$host_is_linux = xyes; then
MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
@@ -445,11 +447,6 @@ MPD_DEPENDS([enable_jack], [enable_glib],
AC_SYS_LARGEFILE
AC_ARG_ENABLE(despotify,
AS_HELP_STRING([--enable-despotify],
[enable support for despotify (default: disable)]),,
[enable_despotify=no])
AC_ARG_ENABLE(soundcloud,
AS_HELP_STRING([--enable-soundcloud],
[enable support for soundcloud.com]),,
@@ -496,11 +493,6 @@ AC_ARG_ENABLE(modplug,
[enable modplug decoder plugin]),,
enable_modplug=auto)
AC_ARG_ENABLE(mp4v2,
AS_HELP_STRING([--enable-mp4v2],
[enable libmp4v2 decoder plugin]),,
enable_mp4v2=auto)
AC_ARG_ENABLE(mpc,
AS_HELP_STRING([--enable-mpc],
[disable musepack (MPC) support (default: auto)]),,
@@ -984,14 +976,6 @@ if test x$enable_nfs = xyes; then
fi
AM_CONDITIONAL(ENABLE_NFS, test x$enable_nfs = xyes)
dnl --------------------------------- Despotify ---------------------------------
MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify],
[Despotify support], [despotify not found])
if test x$enable_despotify = xyes; then
AC_DEFINE(ENABLE_DESPOTIFY, 1, [Define when despotify is enabled])
fi
AM_CONDITIONAL(ENABLE_DESPOTIFY, test x$enable_despotify = xyes)
dnl --------------------------------- Soundcloud ------------------------------
if test x$enable_soundcloud != xno; then
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
@@ -1264,24 +1248,6 @@ if test x$enable_modplug = xyes; then
fi
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
dnl -------------------------------- libmp4v2 ---------------------------------
if test x$enable_aac = xyes; then
MPD_AUTO_LIB(mp4v2, MP4V2, mp4v2, MP4Create, [-lmp4v2], [],
[mp4v2], [libmp4v2 not found])
if test x$enable_mp4v2 = xyes; then
AC_DEFINE(HAVE_MP4V2, 1, [Define to use libmp4v2 for MP4 decoding])
fi
else
if test x$enable_mp4v2 = xyes; then
AC_MSG_ERROR([MP4V2 requires AAC!])
fi
enable_mp4v2=no
fi
AM_CONDITIONAL(HAVE_MP4V2, test x$enable_mp4v2 = xyes)
dnl -------------------------------- libopus ----------------------------------
MPD_AUTO_PKG(opus, OPUS, [opus ogg],
[opus decoder plugin], [libopus not found])
@@ -1868,7 +1834,6 @@ printf '\n\t'
results(sndfile, [libsndfile])
results(mikmod, [MikMod])
results(modplug, [MODPLUG])
results(mp4v2, [MP4V2])
results(mad, [MAD])
results(mpg123, [MPG123])
results(mpc, [Musepack])
@@ -1927,7 +1892,6 @@ printf '\nStreaming support:\n\t'
results(cdio_paranoia, [CDIO_PARANOIA])
results(curl,[CURL])
results(smbclient,[SMBCLIENT])
results(despotify,[Despotify])
results(soundcloud,[Soundcloud])
printf '\n\t'
results(mms,[MMS])

@@ -1,6 +1,7 @@
<?xml version='1.0' encoding="utf-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"docbook/dtd/xml/4.2/docbookx.dtd">
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<book>
<title>The Music Player Daemon - Developer's Manual</title>

154
doc/include/tags.xml Normal file

@@ -0,0 +1,154 @@
<?xml version='1.0' encoding="utf-8"?>
<!DOCTYPE itemizedlist PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<itemizedlist>
<listitem>
<para>
<varname>artist</varname>: the artist name. Its meaning is not
well-defined; see <varname>composer</varname> and
<varname>performer</varname> for more specific tags.
</para>
</listitem>
<listitem>
<para>
<varname>artistsort</varname>: same as
<varname>artist</varname>, but for sorting. This usually omits
prefixes such as "The".
</para>
</listitem>
<listitem>
<para>
<varname>album</varname>: the album name.
</para>
</listitem>
<listitem>
<para>
<varname>albumsort</varname>: same as <varname>album</varname>,
but for sorting.
</para>
</listitem>
<listitem>
<para>
<varname>albumartist</varname>: on multi-artist albums, this is
the artist name which shall be used for the whole album. The
exact meaning of this tag is not well-defined.
</para>
</listitem>
<listitem>
<para>
<varname>albumartistsort</varname>: same as
<varname>albumartist</varname>, but for sorting.
</para>
</listitem>
<listitem>
<para>
<varname>title</varname>: the song title.
</para>
</listitem>
<listitem>
<para>
<varname>track</varname>: the track number within the album.
</para>
</listitem>
<listitem>
<para>
<varname>name</varname>: a name for this song. This is not the
song title. The exact meaning of this tag is not well-defined.
It is often used by badly configured internet radio stations
with broken tags to squeeze both the artist name and the song
title in one tag.
</para>
</listitem>
<listitem>
<para>
<varname>genre</varname>: the music genre.
</para>
</listitem>
<listitem>
<para>
<varname>date</varname>: the song's release date. This is
usually a 4-digit year.
</para>
</listitem>
<listitem>
<para>
<varname>composer</varname>: the artist who composed the song.
</para>
</listitem>
<listitem>
<para>
<varname>performer</varname>: the artist who performed the song.
</para>
</listitem>
<listitem>
<para>
<varname>comment</varname>: a human-readable comment about this
song. The exact meaning of this tag is not well-defined.
</para>
</listitem>
<listitem>
<para>
<varname>disc</varname>: the disc number in a multi-disc album.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_artistid</varname>: the artist id in the
<ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_albumid</varname>: the album id in the
<ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_albumartistid</varname>: the album artist
id in the <ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_trackid</varname>: the track id in the
<ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_releasetrackid</varname>: the release track
id in the <ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
database.
</para>
</listitem>
</itemizedlist>

@@ -174,18 +174,6 @@ MP3 playback.
This specifies whether relative or absolute paths for song filenames are used
when saving playlists. The default is "no".
.TP
.B metadata_to_use <tags>
This specifies the tag types that will be scanned for and made available to
clients. Note that you must recreate (not update) your database for changes to
this parameter to take effect. Possible values are artist, album, title,
track, name, genre, date, composer, performer, comment, disc,
musicbrainz_artistid, musicbrainz_albumid, musicbrainz_albumartistid,
musicbrainz_releasetrackid, musicbrainz_trackid. Multiple tags may be specified
as a comma separated list.
An example value is "artist,album,title,track". The special value "none" may
be used alone to disable all metadata. The default is to use all known tag
types except for comments and those starting with "musicbrainz".
.TP
.B auto_update <yes or no>
This specifies the whether to support automatic update of music database when
files are changed in music_directory. The default is to disable autoupdate
@@ -195,16 +183,6 @@ of database.
Limit the depth of the directories being watched, 0 means only watch
the music directory itself. There is no limit by default.
.TP
.B despotify_user <name>
This specifies the user to use when logging in to Spotify using the despotify plugins.
.TP
.B despotify_password <name>
This specifies the password to use when logging in to Spotify using the despotify plugins.
.TP
.B despotify_high_bitrate <yes or no>
This specifies if the requested bitrate for Spotify should be high or not. Higher sounds
better but requires more processing and higher bandwidth. Default is yes.
.TP
.SH REQUIRED AUDIO OUTPUT PARAMETERS
.TP
.B type <type>

@@ -115,7 +115,7 @@
#
# This setting defines a list of tag types that will be extracted during the
# audio file discovery process. The complete list of possible values can be
# found in the mpd.conf man page.
# found in the user manual.
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
#
# This setting enables automatic update of MPD's database when files in

@@ -1,6 +1,7 @@
<?xml version='1.0' encoding="utf-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"docbook/dtd/xml/4.2/docbookx.dtd">
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<book>
<title>The Music Player Daemon protocol</title>
@@ -201,6 +202,25 @@
omitted, then the maximum possible value is assumed.
</para>
</section>
<section id="tags">
<title>Tags</title>
<para>
The following tags are supported by
<application>MPD</application>:
</para>
<xi:include href="include/tags.xml"
xmlns:xi="http://www.w3.org/2001/XInclude"/>
<para>
There can be multiple values for some of these tags. For
example, <application>MPD</application> may return multiple
lines with a <varname>performer</varname> tag. A tag value is
a UTF-8 string.
</para>
</section>
</chapter>
<chapter id="recipes">
@@ -1141,7 +1161,7 @@ OK
</term>
<listitem>
<para>
Searches case-sensitively for partial matches in the
Searches case-insensitively for partial matches in the
current playlist.
</para>
</listitem>
@@ -1533,6 +1553,7 @@ OK
<command>count</command>
<arg choice="req"><replaceable>TAG</replaceable></arg>
<arg choice="req"><replaceable>NEEDLE</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
<arg choice="opt">group</arg>
<arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
</cmdsynopsis>

@@ -1,6 +1,7 @@
<?xml version='1.0' encoding="utf-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"docbook/dtd/xml/4.2/docbookx.dtd">
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<book>
<title>The Music Player Daemon - User's Manual</title>
@@ -16,7 +17,7 @@
<application>MPD</application> (Music Player Daemon) is, as the
name suggests, a server software allowing you to remotely play
your music, handle playlists, deliver music (HTTP streams with
various sub-protocols) and organizze playlists.
various sub-protocols) and organize playlists.
</para>
<para>
@@ -89,7 +90,7 @@ cd mpd-version</programlisting>
</para>
<programlisting>
apt-get install g++ automake autoconf \
apt-get install g++ \
libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
@@ -98,19 +99,21 @@ apt-get install g++ automake autoconf \
libsidplay2-dev libsidutils-dev libresid-builder-dev \
libavcodec-dev libavformat-dev \
libmp3lame-dev \
libsamplerate0-dev \
libsamplerate0-dev libsoxr-dev \
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
libzzip-dev \
libcurl4-gnutls-dev libyajl-dev \
libcurl4-gnutls-dev libyajl-dev libexpat-dev \
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
libpulse-dev libroar-dev libshout3-dev \
libmpdclient-dev \
libnfs-dev libsmbclient-dev \
libupnp-dev \
libavahi-client-dev \
libsqlite3-dev \
libsystemd-daemon-dev libwrap0-dev \
libcppunit-dev xmlto \
libboost-dev \
libglib2.0-dev
libglib2.0-dev libicu-dev
</programlisting>
<para>
@@ -313,9 +316,8 @@ systemctl start mpd.socket</programlisting>
</para>
<programlisting>input {
plugin "despotify"
user "foo"
password "bar"
plugin "curl"
proxy "proxy.local"
}
</programlisting>
@@ -943,6 +945,33 @@ systemctl start mpd.socket</programlisting>
<section id="config_other">
<title>Other Settings</title>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>metadata_to_use</varname>
<parameter>TAG1,TAG2,...</parameter>
</entry>
<entry>
Use only the specified tags, and ignore the others.
This setting can reduce the database size and
<application>MPD</application>'s memory usage by
omitting unused tags. By default, all tags but
<varname>comment</varname> are enabled. The special
value "none" disables all tags.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
<section>
<title>The State File</title>
@@ -1189,6 +1218,58 @@ database {
plugin).
</para>
</section>
<section id="realtime">
<title>Real-Time Scheduling</title>
<para>
On Linux, <application>MPD</application> attempts to configure
<ulink
url="https://en.wikipedia.org/wiki/Real-time_computing">real-time
scheduling</ulink> for some threads that benefit from it.
</para>
<para>
This is only possible you allow <application>MPD</application>
to do it. This privilege is controlled by
<varname>RLIMIT_RTPRIO</varname>
<varname>RLIMIT_RTTIME</varname>. You can configure this
privilege with <command>ulimit</command> before launching
<application>MPD</application>:
</para>
<programlisting>ulimit -HS -r 50; mpd</programlisting>
<para>
Or you can use the <command>prlimit</command> program from the
<application>util-linux</application> package:
</para>
<programlisting>prlimit --rtprio=50 --rttime=unlimited mpd</programlisting>
<para>
The <application>systemd</application> service file shipped
with <application>MPD</application> comes with this setting.
</para>
<para>
This works only if the Linux kernel was compiled with
<varname>CONFIG_RT_GROUP_SCHED</varname> disabled. Use the
following command to check this option for your current
kernel:
</para>
<programlisting>zgrep ^CONFIG_RT_GROUP_SCHED /proc/config.gz</programlisting>
<note>
<para>
There is a rumor that real-time scheduling improves audio
quality. That is not true. All it does is reduce the
probability of skipping (audio buffer xruns) when the
computer is under heavy load.
</para>
</note>
</section>
</chapter>
<chapter id="use">
@@ -1244,6 +1325,19 @@ database {
</para>
</section>
<section id="tags">
<title>Metadata</title>
<para>
When scanning or playing a song,
<application>MPD</application> parses its metadata. The
following tags are supported:
</para>
<xi:include href="include/tags.xml"
xmlns:xi="http://www.w3.org/2001/XInclude"/>
</section>
<section id="queue">
<title>The queue</title>
@@ -1738,66 +1832,6 @@ buffer_size: 16384</programlisting>
</informaltable>
</section>
<section>
<title><varname>despotify</varname></title>
<para>
Plays <ulink url="http://www.spotify.com">Spotify</ulink> tracks using the despotify
library. The despotify plugin uses a <filename>spt://</filename> URI and a Spotify
URL. So for example, you can add a song with:
</para>
<para>
<filename>mpc add spt://spotify:track:5qENVY0YEdZ7fiuOax70x1</filename>
</para>
<para>
You need a Spotify premium account to use this plugin, and you need
to setup username and password in the configuration file. The
configuration settings are global since the despotify playlist plugin
use the same settings.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>despotify_user</varname>
</entry>
<entry>
Sets up the Spotify username (required)
</entry>
</row>
<row>
<entry>
<varname>despotify_password</varname>
</entry>
<entry>
Sets up the Spotify password (required)
</entry>
</row>
<row>
<entry>
<varname>despotify_high_bitrate</varname>
</entry>
<entry>
Set up if high bitrate should be used for Spotify tunes.
High bitrate sounds better but slow systems can have problems
with playback (default yes).
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>file</varname></title>
@@ -2654,7 +2688,8 @@ buffer_size: 16384</programlisting>
/ <ulink
url="http://icecast.org/"><application>IceCast</application></ulink>.
HTTP streaming clients like
<application>mplayer</application> can connect to it.
<application>mplayer</application>, <application>VLC</application>,
and <application>mpv</application> can connect to it.
</para>
<para>
@@ -3284,70 +3319,6 @@ buffer_size: 16384</programlisting>
playlist files.
</para>
</section>
<section>
<title><varname>despotify</varname></title>
<para>
Adds <ulink url="http://www.spotify.com/">Spotify</ulink>
playlists. Spotify playlists use the <filename>spt://</filename> URI,
and a Spotify playlist URL. So for example, you can load a playlist
with
</para>
<para>
<filename>mpc load spt://spotify:user:simon.kagstrom:playlist:3SUwkOe5VbVHysZcidEZtH</filename>
</para>
<para>
See the despotify input plugin for configuration options (username
and password needs to be setup)
</para>
</section>
<section>
<title><varname>soundcloud</varname></title>
<para>
Adds <ulink url="https://www.soundcloud.com/">Soundcloud</ulink>
playlists. SoundCloud playlists use the <filename>soundcloud://</filename> URI,
and with a number of arguments, you may load different playlists with
</para>
<programlisting>
mpc load soundcloud://track/TRACK_ID
mpc load soundcloud://playlist/PLAYLIST_ID
mpc load soundcloud://user/USERNAME
mpc load soundcloud://search/SEARCH_QUERY
mpc load soundcloud://url/https://soundcloud.com/ARTIST/TRACK-NAME
</programlisting>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>apikey</varname>
<parameter>client_id</parameter>
</entry>
<entry>
User apikey/client_id can override the
<application>MPD</application> token provided by
SoundCloud.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
</section>
</chapter>
</book>

@@ -20,33 +20,45 @@
#ifndef COMPILER_H
#define COMPILER_H
#define GCC_CHECK_VERSION(major, minor) \
(defined(__GNUC__) && \
(__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))))
#define GCC_MAKE_VERSION(major, minor, patchlevel) ((major) * 10000 + (minor) * 100 + patchlevel)
#ifdef __GNUC__
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#define GCC_VERSION GCC_MAKE_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
#else
#define GCC_VERSION 0
#endif
#define GCC_CHECK_VERSION(major, minor) \
(defined(__GNUC__) && GCC_VERSION >= GCC_MAKE_VERSION(major, minor, 0))
/**
* Are we building with gcc (not clang or any other compiler) and a
* version older than the specified one?
*/
#define GCC_OLDER_THAN(major, minor) \
(defined(__GNUC__) && !defined(__clang__) && \
GCC_VERSION < GCC_MAKE_VERSION(major, minor, 0))
#ifdef __clang__
# define CLANG_VERSION (__clang_major__ * 10000 \
+ __clang_minor__ * 100 \
+ __clang_patchlevel__)
# define CLANG_VERSION GCC_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
# if __clang_major__ < 3
# error Sorry, your clang version is too old. You need at least version 3.1.
# endif
#elif defined(__GNUC__)
# if !GCC_CHECK_VERSION(4,6)
# if GCC_OLDER_THAN(4,6)
# error Sorry, your gcc version is too old. You need at least version 4.6.
# endif
#else
# warning Untested compiler. Use at your own risk!
#endif
/**
* Are we building with the specified version of clang or newer?
*/
#define CLANG_CHECK_VERSION(major, minor) \
(defined(__clang__) && \
CLANG_VERSION >= GCC_MAKE_VERSION(major, minor, 0))
#if GCC_CHECK_VERSION(4,0)
/* GCC 4.x */
@@ -141,7 +153,7 @@
#if defined(__cplusplus)
/* support for C++11 "override" was added in gcc 4.7 */
#if !defined(__clang__) && !GCC_CHECK_VERSION(4,7)
#if GCC_OLDER_THAN(4,7)
#define override
#define final
#endif

@@ -188,6 +188,14 @@ public:
tag = std::move(other.tag);
}
/**
* Similar to the MoveTagFrom(), but move only the #TagItem
* array.
*/
void MoveTagItemsFrom(DetachedSong &&other) {
tag.MoveItemsFrom(std::move(other.tag));
}
time_t GetLastModified() const {
return mtime;
}

@@ -76,7 +76,10 @@ idle_get_names(void)
unsigned
idle_parse_name(const char *name)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(name != nullptr);
#endif
for (unsigned i = 0; idle_names[i] != nullptr; ++i)
if (StringEqualsCaseASCII(name, idle_names[i]))

@@ -194,6 +194,12 @@ FileLog(const Domain &domain, const char *message)
domain.GetName(),
chomp_length(message), message);
#ifdef WIN32
/* force-flush the log file, because setvbuf() does not seem
to have an effect on WIN32 */
fflush(stderr);
#endif
#ifdef HAVE_GLIB
g_free(converted);
#endif

@@ -111,6 +111,9 @@ log_early_init(bool verbose)
#ifdef ANDROID
(void)verbose;
#else
/* force stderr to be line-buffered */
setvbuf(stderr, nullptr, _IOLBF, 0);
if (verbose)
SetLogThreshold(LogLevel::DEBUG);
#endif

@@ -54,7 +54,6 @@
#include "system/FatalError.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "thread/Id.hxx"
#include "thread/Slack.hxx"
#include "lib/icu/Init.hxx"
@@ -123,8 +122,6 @@
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
static constexpr Domain main_domain("main");
#ifdef ANDROID
Context *context;
#endif
@@ -633,7 +630,7 @@ static int mpd_main_after_fork(struct options options)
config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
INT_MAX));
#else
FormatWarning(main_domain,
FormatWarning(config_domain,
"inotify: auto_update was disabled. enable during compilation phase");
#endif
}

@@ -612,6 +612,12 @@ Player::ProcessCommand()
queued = true;
pc.CommandFinished();
pc.Unlock();
if (dc.LockIsIdle())
StartDecoder(*new MusicPipe());
pc.Lock();
break;
case PlayerCommand::PAUSE:

@@ -77,7 +77,10 @@ SongFilter::Item::Item(unsigned _tag, time_t _time)
bool
SongFilter::Item::StringMatch(const char *s) const
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(s != nullptr);
#endif
if (fold_case) {
const std::string folded = IcuCaseFold(s);

@@ -77,7 +77,10 @@ SongLoader::LoadFile(const char *path_utf8, Error &error) const
DetachedSong *
SongLoader::LoadSong(const char *uri_utf8, Error &error) const
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(uri_utf8 != nullptr);
#endif
if (memcmp(uri_utf8, "file:///", 8) == 0)
/* absolute path */

@@ -66,7 +66,11 @@ public:
return iso9660_iso_seek_read(iso, ptr, start, i_size);
}
void Visit(const char *path, ArchiveVisitor &visitor);
/**
* @param capacity the path buffer size
*/
void Visit(char *path, size_t length, size_t capacity,
ArchiveVisitor &visitor);
virtual void Close() override {
Unref();
@@ -84,32 +88,36 @@ static constexpr Domain iso9660_domain("iso9660");
/* archive open && listing routine */
inline void
Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor)
Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
ArchiveVisitor &visitor)
{
CdioList_t *entlist;
CdioListNode_t *entnode;
iso9660_stat_t *statbuf;
char pathname[4096];
entlist = iso9660_ifs_readdir (iso, psz_path);
auto *entlist = iso9660_ifs_readdir(iso, path);
if (!entlist) {
return;
}
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
CdioListNode_t *entnode;
_CDIO_LIST_FOREACH (entnode, entlist) {
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
auto *statbuf = (iso9660_stat_t *)
_cdio_list_node_data(entnode);
const char *filename = statbuf->filename;
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
continue;
strcpy(pathname, psz_path);
strcat(pathname, statbuf->filename);
size_t filename_length = strlen(filename);
if (length + filename_length + 1 >= capacity)
/* file name is too long */
continue;
memcpy(path + length, filename, filename_length + 1);
size_t new_length = length + filename_length;
if (iso9660_stat_s::_STAT_DIR == statbuf->type ) {
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
strcat(pathname, "/");
Visit(pathname, visitor);
}
memcpy(path + new_length, "/", 2);
Visit(path, new_length + 1, capacity, visitor);
} else {
//remove leading /
visitor.VisitArchiveEntry(pathname + 1);
visitor.VisitArchiveEntry(path + 1);
}
}
_cdio_list_free (entlist, true);
@@ -133,7 +141,8 @@ iso9660_archive_open(Path pathname, Error &error)
void
Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
{
Visit("/", visitor);
char path[4096] = "/";
Visit(path, 1, sizeof(path), visitor);
}
/* single archive handling */

@@ -168,12 +168,13 @@ bool
ZzipInputStream::Seek(offset_type new_offset, Error &error)
{
zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
if (ofs != -1) {
if (ofs < 0) {
error.Set(zzip_domain, "zzip_seek() has failed");
offset = ofs;
return true;
return false;
}
return false;
offset = ofs;
return true;
}
/* exported structures */

@@ -41,7 +41,7 @@ client_process_command_list(Client &client, bool list_ok,
FormatDebug(client_domain, "process command \"%s\"", cmd);
ret = command_process(client, num++, cmd);
FormatDebug(client_domain, "command returned %i", ret);
FormatDebug(client_domain, "command returned %i", int(ret));
if (ret != CommandResult::OK || client.IsExpired())
break;
else if (list_ok)
@@ -90,7 +90,7 @@ client_process_line(Client &client, char *line)
std::move(cmd_list));
FormatDebug(client_domain,
"[%u] process command "
"list returned %i", client.num, ret);
"list returned %i", client.num, int(ret));
if (ret == CommandResult::CLOSE ||
client.IsExpired())
@@ -126,7 +126,7 @@ client_process_line(Client &client, char *line)
ret = command_process(client, 0, line);
FormatDebug(client_domain,
"[%u] command returned %i",
client.num, ret);
client.num, int(ret));
if (ret == CommandResult::CLOSE ||
client.IsExpired())

@@ -43,7 +43,7 @@ public:
virtual const LightSong *GetSong(const char *uri_utf8,
Error &error) const override;
virtual void ReturnSong(const LightSong *song) const;
void ReturnSong(const LightSong *song) const override;
virtual bool Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,

@@ -103,7 +103,7 @@ public:
virtual void Close() override;
virtual const LightSong *GetSong(const char *uri_utf8,
Error &error) const override;
virtual void ReturnSong(const LightSong *song) const;
void ReturnSong(const LightSong *song) const override;
virtual bool Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,
@@ -731,7 +731,7 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
{
// TODO: eliminate the const_cast
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
return nullptr;
return false;
if (!visit_directory && !visit_playlist && selection.recursive &&
(ServerSupportsSearchBase(connection)
@@ -757,7 +757,7 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
{
// TODO: eliminate the const_cast
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
return nullptr;
return false;
enum mpd_tag_type tag_type2 = Convert(tag_type);
if (tag_type2 == MPD_TAG_COUNT) {
@@ -810,7 +810,7 @@ ProxyDatabase::GetStats(const DatabaseSelection &selection,
// TODO: eliminate the const_cast
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
return nullptr;
return false;
struct mpd_stats *stats2 =
mpd_run_stats(connection);

@@ -435,9 +435,12 @@ SimpleDatabase::Save(Error &error)
bool
SimpleDatabase::Mount(const char *uri, Database *db, Error &error)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(uri != nullptr);
assert(*uri != 0);
assert(db != nullptr);
#endif
assert(*uri != 0);
ScopeDatabaseLock protect;
@@ -445,13 +448,13 @@ SimpleDatabase::Mount(const char *uri, Database *db, Error &error)
if (r.uri == nullptr) {
error.Format(db_domain, DB_CONFLICT,
"Already exists: %s", uri);
return nullptr;
return false;
}
if (strchr(r.uri, '/') != nullptr) {
error.Format(db_domain, DB_NOT_FOUND,
"Parent not found: %s", uri);
return nullptr;
return false;
}
Directory *mnt = r.directory->CreateChild(r.uri);
@@ -478,7 +481,7 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri,
if (cache_path.IsNull()) {
error.Format(db_domain, DB_NOT_FOUND,
"No 'cache_directory' configured");
return nullptr;
return false;
}
std::string name(storage_uri);

@@ -110,9 +110,9 @@ public:
virtual bool Open(Error &error) override;
virtual void Close() override;
virtual const LightSong *GetSong(const char *uri_utf8,
Error &error) const override;
virtual void ReturnSong(const LightSong *song) const;
const LightSong *GetSong(const char *uri_utf8,
Error &error) const override;
void ReturnSong(const LightSong *song) const override;
virtual bool Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,

@@ -85,7 +85,7 @@ public:
virtual void Close() override;
virtual const LightSong *GetSong(const char *uri_utf8,
Error &error) const override;
virtual void ReturnSong(const LightSong *song) const;
void ReturnSong(const LightSong *song) const override;
virtual bool Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,
@@ -101,7 +101,9 @@ public:
virtual bool GetStats(const DatabaseSelection &selection,
DatabaseStats &stats,
Error &error) const override;
virtual time_t GetUpdateStamp() const {return 0;}
time_t GetUpdateStamp() const override {
return 0;
}
protected:
bool Configure(const config_param &param, Error &error);

@@ -334,7 +334,7 @@ UpdateWalk::UpdateDirectory(Directory &directory, const FileInfo &info)
directory_set_stat(directory, info);
Error error;
const std::auto_ptr<StorageDirectoryReader> reader(storage.OpenDirectory(directory.GetPath(), error));
const std::unique_ptr<StorageDirectoryReader> reader(storage.OpenDirectory(directory.GetPath(), error));
if (reader.get() == nullptr) {
LogError(error);
return false;

@@ -433,8 +433,11 @@ update_stream_tag(Decoder &decoder, InputStream *is)
/* no stream tag present - submit the song tag
instead */
decoder.song_tag = nullptr;
}
} else
/* discard the song tag; we don't need it */
delete decoder.song_tag;
decoder.song_tag = nullptr;
delete decoder.stream_tag;
decoder.stream_tag = tag;
@@ -566,7 +569,7 @@ decoder_tag(Decoder &decoder, InputStream *is,
/* save the tag */
delete decoder.decoder_tag;
decoder.decoder_tag = new Tag(tag);
decoder.decoder_tag = new Tag(std::move(tag));
/* check for a new stream tag */

@@ -25,6 +25,7 @@
#include "util/ConstBuffer.hxx"
#include <stddef.h>
#include <stdint.h>
struct Decoder;
class InputStream;

@@ -37,7 +37,6 @@
#include "plugins/MadDecoderPlugin.hxx"
#include "plugins/SndfileDecoderPlugin.hxx"
#include "plugins/Mpg123DecoderPlugin.hxx"
#include "plugins/Mp4v2DecoderPlugin.hxx"
#include "plugins/WildmidiDecoderPlugin.hxx"
#include "plugins/MikmodDecoderPlugin.hxx"
#include "plugins/ModplugDecoderPlugin.hxx"
@@ -55,9 +54,6 @@ const struct DecoderPlugin *const decoder_plugins[] = {
#ifdef HAVE_MPG123
&mpg123_decoder_plugin,
#endif
#ifdef HAVE_MP4V2
&mp4v2_decoder_plugin,
#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif

@@ -26,7 +26,10 @@
bool
DecoderPlugin::SupportsSuffix(const char *suffix) const
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(suffix != nullptr);
#endif
return suffixes != nullptr && string_array_contains(suffixes, suffix);
@@ -35,7 +38,10 @@ DecoderPlugin::SupportsSuffix(const char *suffix) const
bool
DecoderPlugin::SupportsMimeType(const char *mime_type) const
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(mime_type != nullptr);
#endif
return mime_types != nullptr &&
string_array_contains(mime_types, mime_type);

@@ -255,7 +255,11 @@ decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
{
const struct DecoderPlugin *plugin;
#ifdef HAVE_FFMPEG
plugin = decoder_plugin_from_name("ffmpeg");
#else
plugin = decoder_plugin_from_name("mad");
#endif
return plugin != nullptr && plugin->stream_decode != nullptr &&
decoder_stream_decode(*plugin, decoder, is);
}
@@ -380,7 +384,11 @@ decoder_run_song(DecoderControl &dc,
const DetachedSong &song, const char *uri, Path path_fs)
{
Decoder decoder(dc, dc.start_time.IsPositive(),
new Tag(song.GetTag()));
/* pass the song tag only if it's
authoritative, i.e. if it's a local file -
tags on "stream" songs are just remembered
from the last time we played it*/
song.IsFile() ? new Tag(song.GetTag()) : nullptr);
int ret;
dc.state = DecoderState::START;

@@ -29,8 +29,10 @@
#include "input/InputStream.hxx"
#include "tag/TagId3.hxx"
#include "util/Error.hxx"
#include "util/Alloc.hxx"
#include <string.h>
#include <stdlib.h>
#ifdef HAVE_ID3TAG
#include <id3tag.h>
@@ -53,7 +55,7 @@ dsdlib_skip_to(Decoder *decoder, InputStream &is,
offset_type offset)
{
if (is.IsSeekable())
return is.Seek(offset, IgnoreError());
return is.LockSeek(offset, IgnoreError());
if (is.GetOffset() > offset)
return false;
@@ -72,7 +74,7 @@ dsdlib_skip(Decoder *decoder, InputStream &is,
return true;
if (is.IsSeekable())
return is.Seek(is.GetOffset() + delta, IgnoreError());
return is.LockSeek(is.GetOffset() + delta, IgnoreError());
if (delta > 1024 * 1024)
/* don't skip more than one megabyte; it would be too
@@ -123,22 +125,26 @@ dsdlib_tag_id3(InputStream &is,
const id3_length_t count = size - offset;
/* Check and limit id3 tag size to prevent a stack overflow */
id3_byte_t dsdid3[4096];
if (count == 0 || count > sizeof(dsdid3))
if (count < 10 || count > 1024 * 1024)
return;
if (!decoder_read_full(nullptr, is, dsdid3, count))
id3_byte_t *const id3_buf = new id3_byte_t[count];
if (id3_buf == nullptr)
return;
struct id3_tag *id3_tag = id3_tag_parse(dsdid3, count);
if (!decoder_read_full(nullptr, is, id3_buf, count)) {
delete[] id3_buf;
return;
}
struct id3_tag *id3_tag = id3_tag_parse(id3_buf, count);
delete[] id3_buf;
if (id3_tag == nullptr)
return;
scan_id3_tag(id3_tag, handler, handler_ctx);
id3_tag_delete(id3_tag);
return;
}
#endif

@@ -205,7 +205,7 @@ dsdiff_handle_native_tag(InputStream &is,
if (length == 0 || length > 60)
return;
char string[length];
char string[length + 1];
char *label;
label = string;

@@ -92,14 +92,14 @@ struct AvioStream {
AVIOContext *io;
unsigned char buffer[8192];
AvioStream(Decoder *_decoder, InputStream &_input)
:decoder(_decoder), input(_input), io(nullptr) {}
~AvioStream() {
if (io != nullptr)
if (io != nullptr) {
av_free(io->buffer);
av_free(io);
}
}
bool Open();
@@ -153,11 +153,20 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
bool
AvioStream::Open()
{
io = avio_alloc_context(buffer, sizeof(buffer),
constexpr size_t BUFFER_SIZE = 8192;
auto buffer = (unsigned char *)av_malloc(BUFFER_SIZE);
if (buffer == nullptr)
return false;
io = avio_alloc_context(buffer, BUFFER_SIZE,
false, this,
mpd_ffmpeg_stream_read, nullptr,
input.IsSeekable()
? mpd_ffmpeg_stream_seek : nullptr);
/* If avio_alloc_context() fails, who frees the buffer? The
libavformat API documentation does not specify this, it
only says that AVIOContext.buffer must be freed in the end,
however no AVIOContext exists in that failure code path. */
return io != nullptr;
}
@@ -190,10 +199,10 @@ ffmpeg_init(gcc_unused const config_param &param)
}
static int
ffmpeg_find_audio_stream(const AVFormatContext *format_context)
ffmpeg_find_audio_stream(const AVFormatContext &format_context)
{
for (unsigned i = 0; i < format_context->nb_streams; ++i)
if (format_context->streams[i]->codec->codec_type ==
for (unsigned i = 0; i < format_context.nb_streams; ++i)
if (format_context.streams[i]->codec->codec_type ==
AVMEDIA_TYPE_AUDIO)
return i;
@@ -267,22 +276,22 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src,
* Copy PCM data from a AVFrame to an interleaved buffer.
*/
static int
copy_interleave_frame(const AVCodecContext *codec_context,
const AVFrame *frame,
copy_interleave_frame(const AVCodecContext &codec_context,
const AVFrame &frame,
uint8_t **output_buffer,
uint8_t **global_buffer, int *global_buffer_size)
{
int plane_size;
const int data_size =
av_samples_get_buffer_size(&plane_size,
codec_context->channels,
frame->nb_samples,
codec_context->sample_fmt, 1);
codec_context.channels,
frame.nb_samples,
codec_context.sample_fmt, 1);
if (data_size <= 0)
return data_size;
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
codec_context->channels > 1) {
if (av_sample_fmt_is_planar(codec_context.sample_fmt) &&
codec_context.channels > 1) {
if(*global_buffer_size < data_size) {
av_freep(global_buffer);
@@ -294,44 +303,85 @@ copy_interleave_frame(const AVCodecContext *codec_context,
*global_buffer_size = data_size;
}
*output_buffer = *global_buffer;
copy_interleave_frame2(*output_buffer, frame->extended_data,
frame->nb_samples,
codec_context->channels,
av_get_bytes_per_sample(codec_context->sample_fmt));
copy_interleave_frame2(*output_buffer, frame.extended_data,
frame.nb_samples,
codec_context.channels,
av_get_bytes_per_sample(codec_context.sample_fmt));
} else {
*output_buffer = frame->extended_data[0];
*output_buffer = frame.extended_data[0];
}
return data_size;
}
/**
* Convert AVPacket::pts to a stream-relative time stamp (still in
* AVStream::time_base units). Returns a negative value on error.
*/
gcc_pure
static int64_t
StreamRelativePts(const AVPacket &packet, const AVStream &stream)
{
auto pts = packet.pts;
if (pts < 0 || pts == int64_t(AV_NOPTS_VALUE))
return -1;
auto start = start_time_fallback(stream);
return pts - start;
}
/**
* Convert a non-negative stream-relative time stamp in
* AVStream::time_base units to a PCM frame number.
*/
gcc_pure
static uint64_t
PtsToPcmFrame(uint64_t pts, const AVStream &stream,
const AVCodecContext &codec_context)
{
return av_rescale_q(pts, stream.time_base, codec_context.time_base);
}
/**
* @param min_frame skip all data before this PCM frame number; this
* is used after seeking to skip data in an AVPacket until the exact
* desired time stamp has been reached
*/
static DecoderCommand
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
const AVPacket *packet,
AVCodecContext *codec_context,
const AVStream *stream,
AVPacket &&packet,
AVCodecContext &codec_context,
const AVStream &stream,
AVFrame *frame,
uint64_t min_frame, size_t pcm_frame_size,
uint8_t **buffer, int *buffer_size)
{
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
decoder_timestamp(decoder,
time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
stream->time_base));
size_t skip_bytes = 0;
AVPacket packet2 = *packet;
const auto pts = StreamRelativePts(packet, stream);
if (pts >= 0) {
if (min_frame > 0) {
auto cur_frame = PtsToPcmFrame(pts, stream,
codec_context);
if (cur_frame < min_frame)
skip_bytes = pcm_frame_size * (min_frame - cur_frame);
} else
decoder_timestamp(decoder,
time_from_ffmpeg(pts, stream.time_base));
}
uint8_t *output_buffer;
DecoderCommand cmd = DecoderCommand::NONE;
while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
while (packet.size > 0 && cmd == DecoderCommand::NONE) {
int audio_size = 0;
int got_frame = 0;
int len = avcodec_decode_audio4(codec_context,
int len = avcodec_decode_audio4(&codec_context,
frame, &got_frame,
&packet2);
&packet);
if (len >= 0 && got_frame) {
audio_size = copy_interleave_frame(codec_context,
frame,
*frame,
&output_buffer,
buffer, buffer_size);
if (audio_size < 0)
@@ -345,15 +395,27 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is,
break;
}
packet2.data += len;
packet2.size -= len;
packet.data += len;
packet.size -= len;
if (audio_size <= 0)
continue;
const uint8_t *data = output_buffer;
if (skip_bytes > 0) {
if (skip_bytes >= size_t(audio_size)) {
skip_bytes -= audio_size;
continue;
}
data += skip_bytes;
audio_size -= skip_bytes;
skip_bytes = 0;
}
cmd = decoder_data(decoder, is,
output_buffer, audio_size,
codec_context->bit_rate / 1000);
data, audio_size,
codec_context.bit_rate / 1000);
}
return cmd;
}
@@ -371,6 +433,7 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
case AV_SAMPLE_FMT_S32P:
return SampleFormat::S32;
case AV_SAMPLE_FMT_FLT:
case AV_SAMPLE_FMT_FLTP:
return SampleFormat::FLOAT;
@@ -423,10 +486,15 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
avpd.filename = is.GetURI();
#ifdef AVPROBE_SCORE_MIME
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(56, 5, 1)
/* this attribute was added in libav/ffmpeg version 11, but
unfortunately it's "uint8_t" instead of "char", and it's
not "const" - wtf? */
avpd.mime_type = (uint8_t *)const_cast<char *>(is.GetMimeType());
#else
/* API problem fixed in FFmpeg 2.5 */
avpd.mime_type = is.GetMimeType();
#endif
#endif
return av_probe_input_format(&avpd, true);
@@ -465,7 +533,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
return;
}
int audio_stream = ffmpeg_find_audio_stream(format_context);
int audio_stream = ffmpeg_find_audio_stream(*format_context);
if (audio_stream == -1) {
LogError(ffmpeg_domain, "No audio stream inside");
avformat_close_input(&format_context);
@@ -550,6 +618,8 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
uint8_t *interleaved_buffer = nullptr;
int interleaved_buffer_size = 0;
uint64_t min_frame = 0;
DecoderCommand cmd;
do {
AVPacket packet;
@@ -557,16 +627,23 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
/* end of file */
break;
if (packet.stream_index == audio_stream)
if (packet.stream_index == audio_stream) {
cmd = ffmpeg_send_packet(decoder, input,
&packet, codec_context,
av_stream,
std::move(packet),
*codec_context,
*av_stream,
frame,
min_frame, audio_format.GetFrameSize(),
&interleaved_buffer, &interleaved_buffer_size);
else
min_frame = 0;
} else
cmd = decoder_get_command(decoder);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 25, 100)
av_packet_unref(&packet);
#else
av_free_packet(&packet);
#endif
if (cmd == DecoderCommand::SEEK) {
int64_t where =
@@ -574,11 +651,16 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
av_stream->time_base) +
start_time_fallback(*av_stream);
/* AVSEEK_FLAG_BACKWARD asks FFmpeg to seek to
the packet boundary before the seek time
stamp, not after */
if (av_seek_frame(format_context, audio_stream, where,
AVSEEK_FLAG_ANY) < 0)
AVSEEK_FLAG_ANY|AVSEEK_FLAG_BACKWARD) < 0)
decoder_seek_error(decoder);
else {
avcodec_flush_buffers(codec_context);
min_frame = decoder_seek_where_frame(decoder);
decoder_command_finished(decoder);
}
}
@@ -630,7 +712,7 @@ ffmpeg_scan_stream(InputStream &is,
}
ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
int idx = ffmpeg_find_audio_stream(f);
int idx = ffmpeg_find_audio_stream(*f);
if (idx >= 0)
ffmpeg_scan_dictionary(f->streams[idx]->metadata,
handler, handler_ctx);
@@ -659,7 +741,7 @@ static const char *const ffmpeg_suffixes[] = {
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
"ogx", "oma", "ogg", "omg", "opus", "psp", "pva", "qcp", "qt", "r3d", "ra",
"ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
"sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
"sol", "son", "spx", "str", "swf", "tak", "tgi", "tgq", "tgv", "thp", "ts",
"tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
"vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv",
"wve",

@@ -156,8 +156,11 @@ gme_file_decode(Decoder &decoder, Path path_fs)
return;
}
const SignedSongTime song_len = ti->length > 0
? SignedSongTime::FromMS(ti->length)
const int length = ti->play_length;
gme_free_info(ti);
const SignedSongTime song_len = length > 0
? SignedSongTime::FromMS(length)
: SignedSongTime::Negative();
/* initialize the MPD decoder */
@@ -168,7 +171,6 @@ gme_file_decode(Decoder &decoder, Path path_fs)
SampleFormat::S16, GME_CHANNELS,
error)) {
LogError(error);
gme_free_info(ti);
gme_delete(emu);
return;
}
@@ -179,8 +181,8 @@ gme_file_decode(Decoder &decoder, Path path_fs)
if (gme_err != nullptr)
LogWarning(gme_domain, gme_err);
if (ti->length > 0)
gme_set_fade(emu, ti->length);
if (length > 0)
gme_set_fade(emu, length);
/* play */
DecoderCommand cmd;
@@ -196,16 +198,17 @@ gme_file_decode(Decoder &decoder, Path path_fs)
if (cmd == DecoderCommand::SEEK) {
unsigned where = decoder_seek_time(decoder).ToMS();
gme_err = gme_seek(emu, where);
if (gme_err != nullptr)
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
decoder_command_finished(decoder);
decoder_seek_error(decoder);
} else
decoder_command_finished(decoder);
}
if (gme_track_ended(emu))
break;
} while (cmd != DecoderCommand::STOP);
gme_free_info(ti);
gme_delete(emu);
}
@@ -236,9 +239,9 @@ gme_scan_file(Path path_fs,
assert(ti != nullptr);
if (ti->length > 0)
if (ti->play_length > 0)
tag_handler_invoke_duration(handler, handler_ctx,
SongTime::FromMS(ti->length));
SongTime::FromMS(ti->play_length));
if (ti->song != nullptr) {
if (gme_track_count(emu) > 1) {

@@ -1,330 +0,0 @@
/*
* Copyright (C) 2003-2014 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" /* must be first for large file support */
#include "Mp4v2DecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include <mp4v2/mp4v2.h>
#include <neaacdec.h>
#include <cstdio>
#include <cstdlib>
static constexpr Domain mp4v2_decoder_domain("mp4v2");
static MP4TrackId
mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder,
AudioFormat &audio_format, Error &error)
{
unsigned long sample_rate;
const MP4TrackId tracks = MP4GetNumberOfTracks(handle);
for (MP4TrackId id = 1; id <= tracks; id++) {
const char* track_type = MP4GetTrackType(handle, id);
if (track_type == 0)
continue;
const auto obj_type = MP4GetTrackEsdsObjectTypeId(handle, id);
if (obj_type == MP4_INVALID_AUDIO_TYPE)
continue;
if (obj_type == MP4_MPEG4_AUDIO_TYPE) {
const auto mpeg_type = MP4GetTrackAudioMpeg4Type(handle, id);
if (!MP4_IS_MPEG4_AAC_AUDIO_TYPE(mpeg_type))
continue;
} else if (!MP4_IS_AAC_AUDIO_TYPE(obj_type))
continue;
if (decoder == nullptr)
/* found audio track, no decoder */
return id;
unsigned char *buff = nullptr;
unsigned buff_size = 0;
if (!MP4GetTrackESConfiguration(handle, id, &buff, &buff_size))
continue;
uint8_t channels;
int32_t nbytes = NeAACDecInit(decoder, buff, buff_size,
&sample_rate, &channels);
free(buff);
if (nbytes < 0)
/* invalid stream */
continue;
if (!audio_format_init_checked(audio_format, sample_rate,
SampleFormat::S16,
channels,
error))
continue;
return id;
}
error.Set(mp4v2_decoder_domain, "no valid aac track found");
return MP4_INVALID_TRACK_ID;
}
static NeAACDecHandle
mp4_faad_new(MP4FileHandle handle, AudioFormat &audio_format, Error &error)
{
const NeAACDecHandle decoder = NeAACDecOpen();
const NeAACDecConfigurationPtr config =
NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
config->downMatrix = 1;
config->dontUpSampleImplicitSBR = 0;
NeAACDecSetConfiguration(decoder, config);
const auto track = mp4_get_aac_track(handle, decoder, audio_format, error);
if (track == MP4_INVALID_TRACK_ID) {
NeAACDecClose(decoder);
return nullptr;
}
return decoder;
}
static void
mp4_file_decode(Decoder &mpd_decoder, Path path_fs)
{
const MP4FileHandle handle = MP4Read(path_fs.c_str());
if (handle == MP4_INVALID_FILE_HANDLE) {
FormatError(mp4v2_decoder_domain,
"unable to open file");
return;
}
AudioFormat audio_format;
Error error;
const NeAACDecHandle decoder = mp4_faad_new(handle, audio_format, error);
if (decoder == nullptr) {
LogError(error);
MP4Close(handle);
return;
}
const MP4TrackId track = mp4_get_aac_track(handle, nullptr, audio_format, error);
/* initialize the MPD core */
const MP4Timestamp scale = MP4GetTrackTimeScale(handle, track);
const SongTime duration = SongTime::FromScale<uint64_t>(MP4GetTrackDuration(handle, track),
scale);
const MP4SampleId num_samples = MP4GetTrackNumberOfSamples(handle, track);
decoder_initialized(mpd_decoder, audio_format, true, duration);
/* the decoder loop */
DecoderCommand cmd = DecoderCommand::NONE;
for (MP4SampleId sample = 1;
sample < num_samples && cmd != DecoderCommand::STOP;
sample++) {
unsigned char *data = nullptr;
unsigned int data_length = 0;
if (cmd == DecoderCommand::SEEK) {
const MP4Timestamp offset =
decoder_seek_time(mpd_decoder).ToScale(scale);
sample = MP4GetSampleIdFromTime(handle, track, offset,
false);
decoder_command_finished(mpd_decoder);
}
/* read */
if (MP4ReadSample(handle, track, sample, &data, &data_length) == 0) {
FormatError(mp4v2_decoder_domain, "unable to read sample");
break;
}
/* decode it */
NeAACDecFrameInfo frame_info;
const void *const decoded = NeAACDecDecode(decoder, &frame_info, data, data_length);
if (frame_info.error > 0) {
FormatWarning(mp4v2_decoder_domain,
"error decoding AAC stream: %s",
NeAACDecGetErrorMessage(frame_info.error));
break;
}
if (frame_info.channels != audio_format.channels) {
FormatDefault(mp4v2_decoder_domain,
"channel count changed from %u to %u",
audio_format.channels, frame_info.channels);
break;
}
if (frame_info.samplerate != audio_format.sample_rate) {
FormatDefault(mp4v2_decoder_domain,
"sample rate changed from %u to %lu",
audio_format.sample_rate,
(unsigned long)frame_info.samplerate);
break;
}
/* update bit rate and position */
unsigned bit_rate = 0;
if (frame_info.samples > 0) {
bit_rate = frame_info.bytesconsumed * 8.0 *
frame_info.channels * audio_format.sample_rate /
frame_info.samples / 1000 + 0.5;
}
/* send PCM samples to MPD */
cmd = decoder_data(mpd_decoder, nullptr, decoded,
(size_t)frame_info.samples * 2,
bit_rate);
free(data);
}
/* cleanup */
NeAACDecClose(decoder);
MP4Close(handle);
}
static inline void
mp4_safe_invoke_tag(const struct tag_handler *handler, void *handler_ctx,
TagType tag, const char *value)
{
if (value != nullptr)
tag_handler_invoke_tag(handler, handler_ctx, tag, value);
}
static bool
mp4_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
const MP4FileHandle handle = MP4Read(path_fs.c_str());
if (handle == MP4_INVALID_FILE_HANDLE)
return false;
AudioFormat tmp_audio_format;
Error error;
const MP4TrackId id = mp4_get_aac_track(handle, nullptr, tmp_audio_format, error);
if (id == MP4_INVALID_TRACK_ID) {
LogError(error);
MP4Close(handle);
return false;
}
const MP4Timestamp scale = MP4GetTrackTimeScale(handle, id);
const SongTime dur =
SongTime::FromScale<uint64_t>(MP4GetTrackDuration(handle, id),
scale);
tag_handler_invoke_duration(handler, handler_ctx, dur);
const MP4Tags* tags = MP4TagsAlloc();
MP4TagsFetch(tags, handle);
static constexpr struct {
const char *MP4Tags::*p;
TagType tag_type;
} mp4v2_tags[] = {
{ &MP4Tags::name, TAG_NAME },
{ &MP4Tags::artist, TAG_ARTIST },
{ &MP4Tags::albumArtist, TAG_ALBUM_ARTIST },
{ &MP4Tags::album, TAG_ALBUM },
{ &MP4Tags::composer, TAG_COMPOSER },
{ &MP4Tags::comments, TAG_COMMENT },
{ &MP4Tags::genre, TAG_GENRE },
{ &MP4Tags::releaseDate, TAG_DATE },
{ &MP4Tags::sortArtist, TAG_ARTIST_SORT },
{ &MP4Tags::sortAlbumArtist, TAG_ALBUM_ARTIST_SORT },
};
for (const auto &i : mp4v2_tags)
mp4_safe_invoke_tag(handler, handler_ctx,
i.tag_type, tags->*i.p);
char buff[8]; /* tmp buffer for index to string. */
if (tags->track != nullptr) {
sprintf(buff, "%d", tags->track->index);
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, buff);
}
if (tags->disk != nullptr) {
sprintf(buff, "%d", tags->disk->index);
tag_handler_invoke_tag(handler, handler_ctx, TAG_DISC, buff);
}
MP4TagsFree(tags);
MP4Close(handle);
return true;
}
static const char *const mp4_suffixes[] = {
"mp4",
"m4a",
/* "m4p", encrypted */
/* "m4b", audio book */
/* "m4r", ring tones */
/* "m4v", video */
nullptr
};
static const char *const mp4_mime_types[] = {
"application/mp4",
"application/m4a",
"audio/mp4",
"audio/m4a",
/* "audio/m4p", */
/* "audio/m4b", */
/* "audio/m4r", */
/* "audio/m4v", */
nullptr
};
const struct DecoderPlugin mp4v2_decoder_plugin = {
"mp4v2",
nullptr,
nullptr,
nullptr,
mp4_file_decode,
mp4_scan_file,
nullptr,
nullptr,
mp4_suffixes,
mp4_mime_types
};

@@ -1,25 +0,0 @@
/*
* Copyright (C) 2003-2014 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_DECODER_MP4V2_HXX
#define MPD_DECODER_MP4V2_HXX
extern const struct DecoderPlugin mp4v2_decoder_plugin;
#endif

@@ -22,10 +22,12 @@
#include "../DecoderAPI.hxx"
#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "pcm/Traits.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
#include "util/Clamp.hxx"
#include "Log.hxx"
#include <mpc/mpcdec.h>
@@ -42,6 +44,9 @@ struct mpc_decoder_data {
static constexpr Domain mpcdec_domain("mpcdec");
static constexpr SampleFormat mpcdec_sample_format = SampleFormat::S24_P32;
typedef SampleTraits<mpcdec_sample_format> MpcdecSampleTraits;
static mpc_int32_t
mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size)
{
@@ -91,18 +96,15 @@ mpc_getsize_cb(mpc_reader *reader)
}
/* this _looks_ performance-critical, don't de-inline -- eric */
static inline int32_t
static inline MpcdecSampleTraits::value_type
mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
{
/* only doing 16-bit audio for now */
int32_t val;
MpcdecSampleTraits::value_type val;
enum {
bits = 24,
};
const int clip_min = -1 << (bits - 1);
const int clip_max = (1 << (bits - 1)) - 1;
constexpr int bits = MpcdecSampleTraits::BITS;
constexpr auto clip_min = MpcdecSampleTraits::MIN;
constexpr auto clip_max = MpcdecSampleTraits::MAX;
#ifdef MPC_FIXED_POINT
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
@@ -117,16 +119,12 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
val = sample * float_scale;
#endif
if (val < clip_min)
val = clip_min;
else if (val > clip_max)
val = clip_max;
return val;
return Clamp(val, clip_min, clip_max);
}
static void
mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src,
mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
const MPC_SAMPLE_FORMAT *src,
unsigned num_samples)
{
while (num_samples-- > 0)
@@ -162,7 +160,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
Error error;
AudioFormat audio_format;
if (!audio_format_init_checked(audio_format, info.sample_freq,
SampleFormat::S24_P32,
mpcdec_sample_format,
info.channels, error)) {
LogError(error);
mpc_demux_exit(demux);
@@ -214,7 +212,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
mpc_uint32_t ret = frame.samples;
ret *= info.channels;
int32_t chunk[ARRAY_SIZE(sample_buffer)];
MpcdecSampleTraits::value_type chunk[ARRAY_SIZE(sample_buffer)];
mpc_to_mpd_buffer(chunk, sample_buffer, ret);
long bit_rate = vbr_update_bits * audio_format.sample_rate

@@ -214,7 +214,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
ogg_stream_clear(&os);
/* restore the previous file position */
is.Seek(old_offset, IgnoreError());
is.LockSeek(old_offset, IgnoreError());
return result;
}
@@ -441,13 +441,15 @@ mpd_opus_scan_stream(InputStream &is,
if (!oy.ExpectFirstPage(os))
return false;
/* read at most two more pages */
unsigned remaining_pages = 2;
/* read at most 64 more pages */
unsigned remaining_pages = 64;
unsigned remaining_packets = 4;
bool result = false;
ogg_packet packet;
while (true) {
while (remaining_packets > 0) {
int r = ogg_stream_packetout(&os, &packet);
if (r < 0) {
result = false;
@@ -466,6 +468,8 @@ mpd_opus_scan_stream(InputStream &is,
continue;
}
--remaining_packets;
if (packet.b_o_s) {
if (!IsOpusHead(packet))
break;

@@ -85,7 +85,7 @@ public:
char *ReadString() {
uint32_t length;
if (!ReadWord(length))
if (!ReadWord(length) || length >= 65536)
return nullptr;
const char *src = (const char *)Read(length);

@@ -157,8 +157,6 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
unsigned bits_per_sample;
encoder->audio_format = audio_format;
/* FIXME: flac should support 32bit as well */
switch (audio_format.format) {
case SampleFormat::S8:
@@ -178,6 +176,8 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
audio_format.format = SampleFormat::S24_P32;
}
encoder->audio_format = audio_format;
/* allocate the encoder */
encoder->fse = FLAC__stream_encoder_new();
if (encoder->fse == nullptr) {

@@ -66,7 +66,7 @@ struct opus_encoder {
ogg_int64_t granulepos;
opus_encoder():encoder(opus_encoder_plugin) {}
opus_encoder():encoder(opus_encoder_plugin), granulepos(0) {}
};
static constexpr Domain opus_encoder_domain("opus_encoder");

@@ -26,7 +26,6 @@
#include "util/NumberParser.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
extern "C"
{
@@ -60,8 +59,6 @@ struct ShineEncoder {
bool WriteChunk(bool flush);
};
static constexpr Domain shine_encoder_domain("shine_encoder");
inline bool
ShineEncoder::Configure(const config_param &param,
gcc_unused Error &error)

@@ -27,6 +27,8 @@
#include <assert.h>
#include <string.h>
static constexpr uint16_t WAVE_FORMAT_PCM = 1;
struct WaveEncoder {
Encoder encoder;
unsigned bits;
@@ -64,15 +66,15 @@ fill_wave_header(struct wave_header *header, int channels, int bits,
header->id_fmt = ToLE32(0x20746d66);
header->id_data = ToLE32(0x61746164);
/* wave format */
header->format = ToLE16(1); // PCM_FORMAT
/* wave format */
header->format = ToLE16(WAVE_FORMAT_PCM);
header->channels = ToLE16(channels);
header->bits = ToLE16(bits);
header->freq = ToLE32(freq);
header->blocksize = ToLE16(block_size);
header->byterate = ToLE32(freq * block_size);
/* chunk sizes (fake data length) */
/* chunk sizes (fake data length) */
header->fmt_size = ToLE32(16);
header->data_size = ToLE32(data_size);
header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));

@@ -21,9 +21,6 @@
#define MPD_SOCKET_DEFERRED_MONITOR_HXX
#include "check.h"
#include "Compiler.h"
#include <atomic>
class EventLoop;

@@ -179,9 +179,10 @@ EventLoop::Run()
mutex.lock();
HandleDeferred();
busy = false;
const bool _again = again;
mutex.unlock();
if (again)
if (_again)
/* re-evaluate timers because one of the
IdleMonitors may have added a new
timeout */

@@ -53,10 +53,11 @@ public:
children.emplace_back(name, filter);
}
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
virtual void Close();
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error);
/* virtual methods from class Filter */
AudioFormat Open(AudioFormat &af, Error &error) override;
void Close() override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
private:
/**

@@ -34,10 +34,11 @@ class NormalizeFilter final : public Filter {
PcmBuffer buffer;
public:
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
virtual void Close();
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
/* virtual methods from class Filter */
AudioFormat Open(AudioFormat &af, Error &error) override;
void Close() override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
};
static Filter *

@@ -112,10 +112,11 @@ public:
*/
void Update();
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
virtual void Close();
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
/* virtual methods from class Filter */
AudioFormat Open(AudioFormat &af, Error &error) override;
void Close() override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
};
void

@@ -120,10 +120,11 @@ public:
*/
bool Configure(const config_param &param, Error &error);
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
virtual void Close();
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
/* virtual methods from class Filter */
AudioFormat Open(AudioFormat &af, Error &error) override;
void Close() override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
};
bool

@@ -26,7 +26,6 @@
#include "AudioFormat.hxx"
#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include <assert.h>
#include <string.h>
@@ -43,14 +42,13 @@ public:
pv.SetVolume(_volume);
}
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
virtual void Close();
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
/* virtual methods from class Filter */
AudioFormat Open(AudioFormat &af, Error &error) override;
void Close() override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
Error &error) override;
};
static constexpr Domain volume_domain("pcm_volume");
static Filter *
volume_filter_init(gcc_unused const config_param &param,
gcc_unused Error &error)

@@ -103,7 +103,10 @@ static inline void FixSeparators(std::string &s)
std::string
PathToUTF8(const char *path_fs)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(path_fs != nullptr);
#endif
#ifdef HAVE_GLIB
if (fs_charset.empty()) {
@@ -144,7 +147,10 @@ PathToUTF8(const char *path_fs)
char *
PathFromUTF8(const char *path_utf8)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(path_utf8 != nullptr);
#endif
if (fs_charset.empty())
return g_strdup(path_utf8);

@@ -52,7 +52,10 @@ template<typename Traits>
typename Traits::const_pointer
GetBasePathImpl(typename Traits::const_pointer p)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
return sep != nullptr
@@ -64,7 +67,10 @@ template<typename Traits>
typename Traits::string
GetParentPathImpl(typename Traits::const_pointer p)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
if (sep == nullptr)

@@ -57,7 +57,11 @@ struct PathTraitsFS {
gcc_pure gcc_nonnull_all
static const_pointer FindLastSeparator(const_pointer p) {
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
#ifdef WIN32
const_pointer pos = p + GetLength(p);
while (p != pos && !IsSeparator(*pos))
@@ -77,7 +81,11 @@ struct PathTraitsFS {
gcc_pure gcc_nonnull_all
static bool IsAbsolute(const_pointer p) {
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
#ifdef WIN32
if (IsDrive(p) && IsSeparator(p[2]))
return true;
@@ -147,7 +155,11 @@ struct PathTraitsUTF8 {
gcc_pure gcc_nonnull_all
static const_pointer FindLastSeparator(const_pointer p) {
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
return strrchr(p, SEPARATOR);
}
@@ -160,7 +172,11 @@ struct PathTraitsUTF8 {
gcc_pure gcc_nonnull_all
static bool IsAbsolute(const_pointer p) {
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
#ifdef WIN32
if (IsDrive(p) && IsSeparator(p[2]))
return true;

@@ -62,6 +62,7 @@ FileOutputStream::Commit(gcc_unused Error &error)
assert(IsDefined());
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
return true;
}
@@ -71,6 +72,7 @@ FileOutputStream::Cancel()
assert(IsDefined());
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
RemoveFile(path);
}

@@ -160,6 +160,11 @@ AsyncInputStream::SeekDone()
assert(io_thread_inside());
assert(IsSeekPending());
/* we may have reached end-of-file previously, and the
connection may have been closed already; however after
seeking successfully, the connection must be alive again */
open = true;
seek_state = SeekState::NONE;
cond.broadcast();
}

@@ -67,7 +67,7 @@ input_stream_global_init(Error &error)
case InputPlugin::InitResult::UNAVAILABLE:
if (error.IsDefined()) {
FormatError(error,
"Input plugin '%s' is unavailable: ",
"Input plugin '%s' is unavailable",
plugin->name);
error.Clear();
}

@@ -122,7 +122,10 @@ InputStream::IsAvailable()
size_t
InputStream::LockRead(void *ptr, size_t _size, Error &error)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(ptr != nullptr);
#endif
assert(_size > 0);
const ScopeLock protect(mutex);

@@ -54,10 +54,6 @@
#include "plugins/CdioParanoiaInputPlugin.hxx"
#endif
#ifdef ENABLE_DESPOTIFY
#include "plugins/DespotifyInputPlugin.hxx"
#endif
const InputPlugin *const input_plugins[] = {
&input_plugin_file,
#ifdef HAVE_ALSA
@@ -83,9 +79,6 @@ const InputPlugin *const input_plugins[] = {
#endif
#ifdef ENABLE_CDIO_PARANOIA
&input_plugin_cdio_paranoia,
#endif
#ifdef ENABLE_DESPOTIFY
&input_plugin_despotify,
#endif
nullptr
};

@@ -43,6 +43,8 @@
#include <alsa/asoundlib.h>
#include <atomic>
#include <assert.h>
#include <string.h>

@@ -453,6 +453,8 @@ CurlInputStream::RequestDone(CURLcode result, long status)
SeekDone();
else if (!IsReady())
SetReady();
else
cond.broadcast();
}
static void

@@ -1,227 +0,0 @@
/*
* Copyright (C) 2003-2014 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 "DespotifyInputPlugin.hxx"
#include "lib/despotify/DespotifyUtils.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "tag/Tag.hxx"
#include "util/StringUtil.hxx"
#include "Log.hxx"
extern "C" {
#include <despotify.h>
}
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
class DespotifyInputStream final : public InputStream {
struct despotify_session *session;
struct ds_track *track;
Tag tag;
struct ds_pcm_data pcm;
size_t len_available;
bool eof;
DespotifyInputStream(const char *_uri,
Mutex &_mutex, Cond &_cond,
despotify_session *_session,
ds_track *_track)
:InputStream(_uri, _mutex, _cond),
session(_session), track(_track),
tag(mpd_despotify_tag_from_track(*track)),
len_available(0), eof(false) {
memset(&pcm, 0, sizeof(pcm));
/* Despotify outputs pcm data */
SetMimeType("audio/x-mpd-cdda-pcm");
SetReady();
}
public:
~DespotifyInputStream();
static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
Error &error);
void Callback(int sig);
/* virtual methods from InputStream */
bool IsEOF() override {
return eof;
}
Tag *ReadTag() override {
if (tag.IsEmpty())
return nullptr;
Tag *result = new Tag(std::move(tag));
tag.Clear();
return result;
}
size_t Read(void *ptr, size_t size, Error &error) override;
private:
void FillBuffer();
};
inline void
DespotifyInputStream::FillBuffer()
{
/* Wait until there is data */
while (1) {
int rc = despotify_get_pcm(session, &pcm);
if (rc == 0 && pcm.len) {
len_available = pcm.len;
break;
}
if (eof == true)
break;
if (rc < 0) {
LogDebug(despotify_domain, "despotify_get_pcm error");
eof = true;
break;
}
/* Wait a while until next iteration */
usleep(50 * 1000);
}
}
inline void
DespotifyInputStream::Callback(int sig)
{
switch (sig) {
case DESPOTIFY_NEW_TRACK:
break;
case DESPOTIFY_TIME_TELL:
break;
case DESPOTIFY_TRACK_PLAY_ERROR:
LogWarning(despotify_domain, "Track play error");
eof = true;
len_available = 0;
break;
case DESPOTIFY_END_OF_PLAYLIST:
eof = true;
LogDebug(despotify_domain, "End of playlist");
break;
}
}
static void callback(gcc_unused struct despotify_session* ds,
int sig, gcc_unused void* data, void* callback_data)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
ctx->Callback(sig);
}
DespotifyInputStream::~DespotifyInputStream()
{
mpd_despotify_unregister_callback(callback);
despotify_free_track(track);
}
inline InputStream *
DespotifyInputStream::Open(const char *url,
Mutex &mutex, Cond &cond,
gcc_unused Error &error)
{
if (!StringStartsWith(url, "spt://"))
return nullptr;
despotify_session *session = mpd_despotify_get_session();
if (session == nullptr)
return nullptr;
ds_link *ds_link = despotify_link_from_uri(url + 6);
if (!ds_link) {
FormatDebug(despotify_domain, "Can't find %s", url);
return nullptr;
}
if (ds_link->type != LINK_TYPE_TRACK) {
despotify_free_link(ds_link);
return nullptr;
}
ds_track *track = despotify_link_get_track(session, ds_link);
despotify_free_link(ds_link);
if (!track)
return nullptr;
DespotifyInputStream *ctx =
new DespotifyInputStream(url, mutex, cond,
session, track);
if (!mpd_despotify_register_callback(callback, ctx)) {
delete ctx;
return nullptr;
}
if (despotify_play(ctx->session, ctx->track, false) == false) {
mpd_despotify_unregister_callback(callback);
delete ctx;
return nullptr;
}
return ctx;
}
static InputStream *
input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error)
{
return DespotifyInputStream::Open(url, mutex, cond, error);
}
size_t
DespotifyInputStream::Read(void *ptr, size_t read_size,
gcc_unused Error &error)
{
if (len_available == 0)
FillBuffer();
size_t to_cpy = std::min(read_size, len_available);
memcpy(ptr, pcm.buf, to_cpy);
len_available -= to_cpy;
offset += to_cpy;
return to_cpy;
}
const InputPlugin input_plugin_despotify = {
"despotify",
nullptr,
nullptr,
input_despotify_open,
};

@@ -1,25 +0,0 @@
/*
* Copyright (C) 2003-2014 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 INPUT_DESPOTIFY_HXX
#define INPUT_DESPOTIFY_HXX
extern const struct InputPlugin input_plugin_despotify;
#endif

@@ -43,7 +43,7 @@ protected:
virtual size_t ThreadRead(void *ptr, size_t size,
Error &error) override;
virtual void Close() {
void Close() override {
mmsx_close(mms);
}
};
@@ -92,6 +92,13 @@ input_mms_open(const char *url,
size_t
MmsInputStream::ThreadRead(void *ptr, size_t read_size, Error &error)
{
/* unfortunately, mmsx_read() blocks until the whole buffer
has been filled; to avoid big latencies, limit the size of
each chunk we read to a reasonable size */
constexpr size_t MAX_CHUNK = 16384;
if (read_size > MAX_CHUNK)
read_size = MAX_CHUNK;
int nbytes = mmsx_read(nullptr, mms, (char *)ptr, read_size);
if (nbytes <= 0) {
if (nbytes < 0)

@@ -132,6 +132,7 @@ SmbclientInputStream::Read(void *ptr, size_t read_size, Error &error)
nbytes = 0;
}
offset += nbytes;
return nbytes;
}

@@ -28,7 +28,7 @@
*/
#ifndef JAVA_FILE_HXX
#define JAVA_FILE_HPP
#define JAVA_FILE_HXX
#include "Object.hxx"

@@ -1,153 +0,0 @@
/*
* Copyright (C) 2003-2014 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 "DespotifyUtils.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "config/ConfigGlobal.hxx"
#include "config/ConfigOption.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
extern "C" {
#include <despotify.h>
}
#include <stdio.h>
const Domain despotify_domain("despotify");
static struct despotify_session *g_session;
static void (*registered_callbacks[8])(struct despotify_session *,
int, void *, void *);
static void *registered_callback_data[8];
static void
callback(struct despotify_session* ds, int sig,
void *data, gcc_unused void *callback_data)
{
size_t i;
for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i];
void *cb_data = registered_callback_data[i];
if (cb)
cb(ds, sig, data, cb_data);
}
}
bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
void *cb_data)
{
size_t i;
for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
if (!registered_callbacks[i]) {
registered_callbacks[i] = cb;
registered_callback_data[i] = cb_data;
return true;
}
}
return false;
}
void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *))
{
size_t i;
for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
if (registered_callbacks[i] == cb) {
registered_callbacks[i] = nullptr;
}
}
}
Tag
mpd_despotify_tag_from_track(const ds_track &track)
{
char tracknum[20];
char comment[80];
char date[20];
if (!track.has_meta_data)
return Tag();
TagBuilder tag;
snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber);
snprintf(date, sizeof(date), "%d", track.year);
snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
track.file_bitrate / 1000,
track.geo_restricted ? "" : "not ");
tag.AddItem(TAG_TITLE, track.title);
tag.AddItem(TAG_ARTIST, track.artist->name);
tag.AddItem(TAG_TRACK, tracknum);
tag.AddItem(TAG_ALBUM, track.album);
tag.AddItem(TAG_DATE, date);
tag.AddItem(TAG_COMMENT, comment);
tag.SetDuration(SignedSongTime::FromMS(track.length));
return tag.Commit();
}
struct despotify_session *mpd_despotify_get_session(void)
{
const char *user;
const char *passwd;
bool high_bitrate;
if (g_session)
return g_session;
user = config_get_string(CONF_DESPOTIFY_USER, nullptr);
passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr);
high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
if (user == nullptr || passwd == nullptr) {
LogDebug(despotify_domain,
"disabling despotify because account is not configured");
return nullptr;
}
if (!despotify_init()) {
LogWarning(despotify_domain, "Can't initialize despotify");
return nullptr;
}
g_session = despotify_init_client(callback, nullptr,
high_bitrate, true);
if (!g_session) {
LogWarning(despotify_domain,
"Can't initialize despotify client");
return nullptr;
}
if (!despotify_authenticate(g_session, user, passwd)) {
LogWarning(despotify_domain,
"Can't authenticate despotify session");
despotify_exit(g_session);
return nullptr;
}
return g_session;
}

@@ -1,71 +0,0 @@
/*
* Copyright (C) 2003-2014 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_DESPOTIFY_H
#define MPD_DESPOTIFY_H
struct Tag;
struct despotify_session;
struct ds_track;
extern const class Domain despotify_domain;
/**
* Return the current despotify session.
*
* If the session isn't initialized, this function will initialize
* it and connect to Spotify.
*
* @return a pointer to the despotify session, or nullptr if it can't
* be initialized (e.g., if the configuration isn't supplied)
*/
struct despotify_session *mpd_despotify_get_session(void);
/**
* Create a MPD tags structure from a spotify track
*
* @param track the track to convert
*
* @return filled in #Tag structure
*/
Tag
mpd_despotify_tag_from_track(const ds_track &track);
/**
* Register a despotify callback.
*
* Despotify calls this e.g., when a track ends.
*
* @param cb the callback
* @param cb_data the data to pass to the callback
*
* @return true if the callback could be registered
*/
bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
void *cb_data);
/**
* Unregister a despotify callback.
*
* @param cb the callback to unregister.
*/
void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *));
#endif

@@ -121,8 +121,11 @@ gcc_pure
int
IcuCollate(const char *a, const char *b)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(a != nullptr);
assert(b != nullptr);
#endif
#ifdef HAVE_ICU
assert(collator != nullptr);
@@ -159,7 +162,10 @@ IcuCaseFold(const char *src)
{
#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())

@@ -20,7 +20,9 @@
#include "config.h"
#include "Blocking.hxx"
#include "Connection.hxx"
#include "Domain.hxx"
#include "event/Call.hxx"
#include "util/Error.hxx"
bool
BlockingNfsOperation::Run(Error &_error)
@@ -31,7 +33,10 @@ BlockingNfsOperation::Run(Error &_error)
[this](){ connection.AddLease(*this); });
/* wait for completion */
LockWaitFinished();
if (!LockWaitFinished()) {
_error.Set(nfs_domain, 0, "Timeout");
return false;
}
/* check for error */
if (error.IsDefined()) {

@@ -35,6 +35,8 @@ class NfsConnection;
* thread, and method Run() waits for completion.
*/
class BlockingNfsOperation : protected NfsCallback, NfsLease {
static constexpr unsigned timeout_ms = 60000;
Mutex mutex;
Cond cond;
@@ -52,10 +54,13 @@ public:
bool Run(Error &error);
private:
void LockWaitFinished() {
bool LockWaitFinished() {
const ScopeLock protect(mutex);
while (!finished)
cond.wait(mutex);
if (!cond.timed_wait(mutex, timeout_ms))
return false;
return true;
}
/**

@@ -157,6 +157,12 @@ public:
return *i;
}
template<typename F>
void ForEach(F &&f) {
for (CT &i : list)
f(i);
}
};
#endif

@@ -34,11 +34,15 @@ extern "C" {
#include <poll.h> /* for POLLIN, POLLOUT */
static constexpr unsigned NFS_MOUNT_TIMEOUT = 60;
inline bool
NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
const char *path,
Error &error)
{
assert(connection.GetEventLoop().IsInside());
int result = nfs_stat_async(ctx, path, Callback, this);
if (result < 0) {
error.Format(nfs_domain, "nfs_stat_async() failed: %s",
@@ -54,6 +58,8 @@ NfsConnection::CancellableCallback::OpenDirectory(nfs_context *ctx,
const char *path,
Error &error)
{
assert(connection.GetEventLoop().IsInside());
int result = nfs_opendir_async(ctx, path, Callback, this);
if (result < 0) {
error.Format(nfs_domain, "nfs_opendir_async() failed: %s",
@@ -69,6 +75,8 @@ NfsConnection::CancellableCallback::Open(nfs_context *ctx,
const char *path, int flags,
Error &error)
{
assert(connection.GetEventLoop().IsInside());
int result = nfs_open_async(ctx, path, flags,
Callback, this);
if (result < 0) {
@@ -85,6 +93,8 @@ NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
struct nfsfh *fh,
Error &error)
{
assert(connection.GetEventLoop().IsInside());
int result = nfs_fstat_async(ctx, fh, Callback, this);
if (result < 0) {
error.Format(nfs_domain, "nfs_fstat_async() failed: %s",
@@ -100,6 +110,8 @@ NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
uint64_t offset, size_t size,
Error &error)
{
assert(connection.GetEventLoop().IsInside());
int result = nfs_pread_async(ctx, fh, offset, size, Callback, this);
if (result < 0) {
error.Format(nfs_domain, "nfs_pread_async() failed: %s",
@@ -113,6 +125,7 @@ NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
inline void
NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh)
{
assert(connection.GetEventLoop().IsInside());
assert(!open);
assert(close_fh == nullptr);
assert(fh != nullptr);
@@ -121,9 +134,22 @@ NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh)
Cancel();
}
inline void
NfsConnection::CancellableCallback::PrepareDestroyContext()
{
assert(IsCancelled());
if (close_fh != nullptr) {
connection.InternalClose(close_fh);
close_fh = nullptr;
}
}
inline void
NfsConnection::CancellableCallback::Callback(int err, void *data)
{
assert(connection.GetEventLoop().IsInside());
if (!IsCancelled()) {
assert(close_fh == nullptr);
@@ -143,8 +169,10 @@ NfsConnection::CancellableCallback::Callback(int err, void *data)
allocated file handle immediately */
assert(close_fh == nullptr);
struct nfsfh *fh = (struct nfsfh *)data;
connection.Close(fh);
if (err >= 0) {
struct nfsfh *fh = (struct nfsfh *)data;
connection.Close(fh);
}
} else if (close_fh != nullptr)
connection.DeferClose(close_fh);
@@ -209,6 +237,7 @@ NfsConnection::RemoveLease(NfsLease &lease)
bool
NfsConnection::Stat(const char *path, NfsCallback &callback, Error &error)
{
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, false);
@@ -225,6 +254,7 @@ bool
NfsConnection::OpenDirectory(const char *path, NfsCallback &callback,
Error &error)
{
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, true);
@@ -240,12 +270,16 @@ NfsConnection::OpenDirectory(const char *path, NfsCallback &callback,
const struct nfsdirent *
NfsConnection::ReadDirectory(struct nfsdir *dir)
{
assert(GetEventLoop().IsInside());
return nfs_readdir(context, dir);
}
void
NfsConnection::CloseDirectory(struct nfsdir *dir)
{
assert(GetEventLoop().IsInside());
return nfs_closedir(context, dir);
}
@@ -253,6 +287,7 @@ bool
NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
Error &error)
{
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, true);
@@ -268,6 +303,7 @@ NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
bool
NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error)
{
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, false);
@@ -284,6 +320,7 @@ bool
NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size,
NfsCallback &callback, Error &error)
{
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, false);
@@ -307,10 +344,22 @@ DummyCallback(int, struct nfs_context *, void *, void *)
{
}
inline void
NfsConnection::InternalClose(struct nfsfh *fh)
{
assert(GetEventLoop().IsInside());
assert(context != nullptr);
assert(fh != nullptr);
nfs_close_async(context, fh, DummyCallback, nullptr);
}
void
NfsConnection::Close(struct nfsfh *fh)
{
nfs_close_async(context, fh, DummyCallback, nullptr);
assert(GetEventLoop().IsInside());
InternalClose(fh);
ScheduleSocket();
}
@@ -327,8 +376,26 @@ NfsConnection::DestroyContext()
assert(GetEventLoop().IsInside());
assert(context != nullptr);
#ifndef NDEBUG
assert(!in_destroy);
in_destroy = true;
#endif
if (!mount_finished) {
assert(TimeoutMonitor::IsActive());
TimeoutMonitor::Cancel();
}
/* cancel pending DeferredMonitor that was scheduled to notify
new leases */
DeferredMonitor::Cancel();
if (SocketMonitor::IsDefined())
SocketMonitor::Cancel();
SocketMonitor::Steal();
callbacks.ForEach([](CancellableCallback &c){
c.PrepareDestroyContext();
});
nfs_destroy_context(context);
context = nullptr;
@@ -337,8 +404,11 @@ NfsConnection::DestroyContext()
inline void
NfsConnection::DeferClose(struct nfsfh *fh)
{
assert(GetEventLoop().IsInside());
assert(in_event);
assert(in_service);
assert(context != nullptr);
assert(fh != nullptr);
deferred_close.push_front(fh);
}
@@ -346,6 +416,7 @@ NfsConnection::DeferClose(struct nfsfh *fh)
void
NfsConnection::ScheduleSocket()
{
assert(GetEventLoop().IsInside());
assert(context != nullptr);
if (!SocketMonitor::IsDefined()) {
@@ -360,9 +431,35 @@ NfsConnection::ScheduleSocket()
SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
}
inline int
NfsConnection::Service(unsigned flags)
{
assert(GetEventLoop().IsInside());
assert(context != nullptr);
#ifndef NDEBUG
assert(!in_event);
in_event = true;
assert(!in_service);
in_service = true;
#endif
int result = nfs_service(context, events_to_libnfs(flags));
#ifndef NDEBUG
assert(context != nullptr);
assert(in_service);
in_service = false;
#endif
return result;
}
bool
NfsConnection::OnSocketReady(unsigned flags)
{
assert(GetEventLoop().IsInside());
assert(deferred_close.empty());
bool closed = false;
@@ -374,21 +471,10 @@ NfsConnection::OnSocketReady(unsigned flags)
re-register it each time */
SocketMonitor::Steal();
assert(!in_event);
in_event = true;
assert(!in_service);
in_service = true;
int result = nfs_service(context, events_to_libnfs(flags));
assert(context != nullptr);
assert(in_service);
in_service = false;
const int result = Service(flags);
while (!deferred_close.empty()) {
nfs_close_async(context, deferred_close.front(),
DummyCallback, nullptr);
InternalClose(deferred_close.front());
deferred_close.pop_front();
}
@@ -405,13 +491,13 @@ NfsConnection::OnSocketReady(unsigned flags)
error.Format(nfs_domain, "NFS connection has failed: %s",
nfs_get_error(context));
BroadcastError(std::move(error));
DestroyContext();
closed = true;
BroadcastError(std::move(error));
} else if (SocketMonitor::IsDefined() && nfs_get_fd(context) < 0) {
} else if (nfs_get_fd(context) < 0) {
/* this happens when rpc_reconnect_requeue() is called
after the connection broke, but autoreconnet was
after the connection broke, but autoreconnect was
disabled - nfs_service() returns 0 */
Error error;
const char *msg = nfs_get_error(context);
@@ -421,14 +507,18 @@ NfsConnection::OnSocketReady(unsigned flags)
error.Format(nfs_domain,
"NFS socket disappeared: %s", msg);
BroadcastError(std::move(error));
DestroyContext();
closed = true;
BroadcastError(std::move(error));
}
assert(context == nullptr || nfs_get_fd(context) >= 0);
#ifndef NDEBUG
assert(in_event);
in_event = false;
#endif
if (context != nullptr)
ScheduleSocket();
@@ -440,10 +530,14 @@ inline void
NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs,
gcc_unused void *data)
{
assert(GetEventLoop().IsInside());
assert(context == nfs);
mount_finished = true;
assert(TimeoutMonitor::IsActive() || in_destroy);
TimeoutMonitor::Cancel();
if (status < 0) {
postponed_mount_error.Format(nfs_domain, status,
"nfs_mount_async() failed: %s",
@@ -464,6 +558,7 @@ NfsConnection::MountCallback(int status, nfs_context *nfs, void *data,
inline bool
NfsConnection::MountInternal(Error &error)
{
assert(GetEventLoop().IsInside());
assert(context == nullptr);
context = nfs_init_context();
@@ -474,8 +569,14 @@ NfsConnection::MountInternal(Error &error)
postponed_mount_error.Clear();
mount_finished = false;
TimeoutMonitor::ScheduleSeconds(NFS_MOUNT_TIMEOUT);
#ifndef NDEBUG
in_service = false;
in_event = false;
in_destroy = false;
#endif
if (nfs_mount_async(context, server.c_str(), export_name.c_str(),
MountCallback, this) != 0) {
@@ -531,9 +632,23 @@ NfsConnection::BroadcastError(Error &&error)
BroadcastMountError(std::move(error));
}
void
NfsConnection::OnTimeout()
{
assert(GetEventLoop().IsInside());
assert(!mount_finished);
mount_finished = true;
DestroyContext();
BroadcastMountError(Error(nfs_domain, "Mount timeout"));
}
void
NfsConnection::RunDeferred()
{
assert(GetEventLoop().IsInside());
if (context == nullptr) {
Error error;
if (!MountInternal(error)) {

@@ -23,6 +23,7 @@
#include "Lease.hxx"
#include "Cancellable.hxx"
#include "event/SocketMonitor.hxx"
#include "event/TimeoutMonitor.hxx"
#include "event/DeferredMonitor.hxx"
#include "util/Error.hxx"
@@ -40,7 +41,7 @@ class NfsCallback;
/**
* An asynchronous connection to a NFS server.
*/
class NfsConnection : SocketMonitor, DeferredMonitor {
class NfsConnection : SocketMonitor, TimeoutMonitor, DeferredMonitor {
class CancellableCallback : public CancellablePointer<NfsCallback> {
NfsConnection &connection;
@@ -84,6 +85,13 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
*/
void CancelAndScheduleClose(struct nfsfh *fh);
/**
* Called by NfsConnection::DestroyContext() right
* before nfs_destroy_context(). This object is given
* a chance to prepare for the latter.
*/
void PrepareDestroyContext();
private:
static void Callback(int err, struct nfs_context *nfs,
void *data, void *private_data);
@@ -111,6 +119,7 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
Error postponed_mount_error;
#ifndef NDEBUG
/**
* True when nfs_service() is being called.
*/
@@ -122,13 +131,20 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
*/
bool in_event;
/**
* True when DestroyContext() is being called.
*/
bool in_destroy;
#endif
bool mount_finished;
public:
gcc_nonnull_all
NfsConnection(EventLoop &_loop,
const char *_server, const char *_export_name)
:SocketMonitor(_loop), DeferredMonitor(_loop),
:SocketMonitor(_loop), TimeoutMonitor(_loop),
DeferredMonitor(_loop),
server(_server), export_name(_export_name),
context(nullptr) {}
@@ -184,6 +200,11 @@ protected:
private:
void DestroyContext();
/**
* Wrapper for nfs_close_async().
*/
void InternalClose(struct nfsfh *fh);
/**
* Invoke nfs_close_async() after nfs_service() returns.
*/
@@ -200,9 +221,17 @@ private:
void ScheduleSocket();
/**
* Wrapper for nfs_service().
*/
int Service(unsigned flags);
/* virtual methods from SocketMonitor */
virtual bool OnSocketReady(unsigned flags) override;
/* virtual methods from TimeoutMonitor */
void OnTimeout() final;
/* virtual methods from DeferredMonitor */
virtual void RunDeferred() override;
};

@@ -56,8 +56,18 @@ NfsFileReader::Close()
return;
}
/* this cancels State::MOUNT */
connection->RemoveLease(*this);
CancelOrClose();
}
void
NfsFileReader::CancelOrClose()
{
assert(state != State::INITIAL &&
state != State::DEFER);
if (state == State::IDLE)
/* no async operation in progress: can close
immediately */
@@ -164,6 +174,8 @@ NfsFileReader::OnNfsConnectionFailed(const Error &error)
{
assert(state == State::MOUNT);
state = State::INITIAL;
Error copy;
copy.Set(error);
OnNfsFileError(std::move(copy));
@@ -174,7 +186,7 @@ NfsFileReader::OnNfsConnectionDisconnected(const Error &error)
{
assert(state > State::MOUNT);
state = State::INITIAL;
CancelOrClose();
Error copy;
copy.Set(error);
@@ -246,6 +258,30 @@ NfsFileReader::OnNfsCallback(unsigned status, void *data)
void
NfsFileReader::OnNfsError(Error &&error)
{
switch (state) {
case State::INITIAL:
case State::DEFER:
case State::MOUNT:
case State::IDLE:
assert(false);
gcc_unreachable();
case State::OPEN:
connection->RemoveLease(*this);
state = State::INITIAL;
break;
case State::STAT:
connection->RemoveLease(*this);
connection->Close(fh);
state = State::INITIAL;
break;
case State::READ:
state = State::IDLE;
break;
}
OnNfsFileError(std::move(error));
}

@@ -24,6 +24,7 @@
#include "Lease.hxx"
#include "Callback.hxx"
#include "event/DeferredMonitor.hxx"
#include "Compiler.h"
#include <string>
@@ -75,6 +76,12 @@ protected:
virtual void OnNfsFileError(Error &&error) = 0;
private:
/**
* Cancel the current operation, if any. The NfsLease must be
* unregistered already.
*/
void CancelOrClose();
void OpenCallback(nfsfh *_fh);
void StatCallback(const struct stat *st);

@@ -29,8 +29,10 @@ NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error)
{
FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName());
manager.connections.erase(manager.connections.iterator_to(*this));
delete this;
/* defer deletion so the caller
(i.e. NfsConnection::OnSocketReady()) can still use this
object */
manager.ScheduleDelete(*this);
}
inline bool
@@ -59,7 +61,9 @@ NfsManager::Compare::operator()(const ManagedConnection &a,
NfsManager::~NfsManager()
{
assert(loop.IsInside());
assert(GetEventLoop().IsInside());
CollectGarbage();
connections.clear_and_dispose([](ManagedConnection *c){
delete c;
@@ -71,13 +75,13 @@ NfsManager::GetConnection(const char *server, const char *export_name)
{
assert(server != nullptr);
assert(export_name != nullptr);
assert(loop.IsInside());
assert(GetEventLoop().IsInside());
Map::insert_commit_data hint;
auto result = connections.insert_check(LookupKey{server, export_name},
Compare(), hint);
if (result.second) {
auto c = new ManagedConnection(*this, loop,
auto c = new ManagedConnection(*this, GetEventLoop(),
server, export_name);
connections.insert_commit(*c, hint);
return *c;
@@ -85,3 +89,19 @@ NfsManager::GetConnection(const char *server, const char *export_name)
return *result.first;
}
}
void
NfsManager::CollectGarbage()
{
assert(GetEventLoop().IsInside());
garbage.clear_and_dispose([](ManagedConnection *c){
delete c;
});
}
void
NfsManager::OnIdle()
{
CollectGarbage();
}

@@ -23,14 +23,16 @@
#include "check.h"
#include "Connection.hxx"
#include "Compiler.h"
#include "event/IdleMonitor.hxx"
#include <boost/intrusive/set.hpp>
#include <boost/intrusive/slist.hpp>
/**
* A manager for NFS connections. Handles multiple connections to
* multiple NFS servers.
*/
class NfsManager {
class NfsManager final : IdleMonitor {
struct LookupKey {
const char *server;
const char *export_name;
@@ -38,6 +40,7 @@ class NfsManager {
class ManagedConnection final
: public NfsConnection,
public boost::intrusive::slist_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
public boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
NfsManager &manager;
@@ -63,8 +66,6 @@ class NfsManager {
const LookupKey b) const;
};
EventLoop &loop;
/**
* Maps server and export_name to #ManagedConnection.
*/
@@ -74,9 +75,18 @@ class NfsManager {
Map connections;
typedef boost::intrusive::slist<ManagedConnection> List;
/**
* A list of "garbage" connection objects. Their destruction
* is postponed because they were thrown into the garbage list
* when callers on the stack were still using them.
*/
List garbage;
public:
NfsManager(EventLoop &_loop)
:loop(_loop) {}
:IdleMonitor(_loop) {}
/**
* Must be run from EventLoop's thread.
@@ -86,6 +96,21 @@ public:
gcc_pure
NfsConnection &GetConnection(const char *server,
const char *export_name);
private:
void ScheduleDelete(ManagedConnection &c) {
connections.erase(connections.iterator_to(c));
garbage.push_front(c);
IdleMonitor::Schedule();
}
/**
* Delete all connections on the #garbage list.
*/
void CollectGarbage();
/* virtual methods from IdleMonitor */
void OnIdle() override;
};
#endif

@@ -58,9 +58,6 @@ static const char *remoteUrlPrefixes[] = {
#ifdef ENABLE_CDIO_PARANOIA
"cdda://",
#endif
#ifdef ENABLE_DESPOTIFY
"spt://",
#endif
#ifdef HAVE_ALSA
"alsa://",
#endif

@@ -25,13 +25,10 @@
#include "output/Internal.hxx"
#include "pcm/Volume.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include <assert.h>
static constexpr Domain mixer_domain("mixer");
static int
output_mixer_get_volume(const AudioOutput &ao)
{

@@ -218,7 +218,7 @@ PulseMixer::SetVolume(unsigned new_volume, Error &error)
struct pa_cvolume cvolume;
pa_cvolume_set(&cvolume, volume.channels,
(pa_volume_t)new_volume * PA_VOLUME_NORM / 100 + 0.5);
(new_volume * PA_VOLUME_NORM + 50) / 100);
bool success = pulse_output_set_volume(output, &cvolume, error);
if (success)
volume = cvolume;

@@ -28,7 +28,7 @@ struct notify {
Cond cond;
bool pending;
#if !defined(WIN32) && !defined(__NetBSD__) && !defined(__BIONIC__)
#ifdef __GLIBC__
constexpr
#endif
notify():pending(false) {}

@@ -48,6 +48,7 @@
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin)
:plugin(_plugin),
mixer(nullptr),
enabled(true), really_enabled(false),
open(false),
pause(false),

@@ -30,6 +30,7 @@
#include "Internal.hxx"
#include "PlayerControl.hxx"
#include "mixer/MixerControl.hxx"
#include "mixer/Volume.hxx"
#include "Idle.hxx"
extern unsigned audio_output_state_version;
@@ -47,6 +48,11 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
ao.enabled = true;
idle_add(IDLE_OUTPUT);
if (ao.mixer != nullptr) {
InvalidateHardwareVolume();
idle_add(IDLE_MIXER);
}
ao.player_control->UpdateAudio();
++audio_output_state_version;
@@ -70,6 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
Mixer *mixer = ao.mixer;
if (mixer != nullptr) {
mixer_close(mixer);
InvalidateHardwareVolume();
idle_add(IDLE_MIXER);
}
@@ -94,6 +101,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
Mixer *mixer = ao.mixer;
if (mixer != nullptr) {
mixer_close(mixer);
InvalidateHardwareVolume();
idle_add(IDLE_MIXER);
}
}

@@ -184,7 +184,8 @@ AudioOutput::LockUpdate(const AudioFormat audio_format,
const ScopeLock protect(mutex);
if (enabled && really_enabled) {
if (fail_timer.Check(REOPEN_AFTER * 1000)) {
if (!fail_timer.IsDefined() ||
fail_timer.Check(REOPEN_AFTER * 1000)) {
return Open(audio_format, mp);
}
} else if (IsOpen())

@@ -22,7 +22,6 @@
#include "../OutputAPI.hxx"
#include "config/ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include <string>
@@ -44,8 +43,6 @@ struct PipeOutput {
bool Configure(const config_param &param, Error &error);
};
static constexpr Domain pipe_output_domain("pipe_output");
inline bool
PipeOutput::Configure(const config_param &param, Error &error)
{

@@ -109,7 +109,7 @@ ShoutOutput::Configure(const config_param &param, Error &error)
if (!audio_format.IsFullyDefined()) {
error.Set(config_domain,
"Need full audio format specification");
return nullptr;
return false;
}
const char *host = require_block_string(param, "host");

@@ -44,7 +44,7 @@ PcmFormatConverter::Open(SampleFormat _src_format, SampleFormat _dest_format,
"PCM conversion from %s to %s is not implemented",
sample_format_to_string(_src_format),
sample_format_to_string(_dest_format));
return nullptr;
return false;
case SampleFormat::S16:
case SampleFormat::S24_P32:

@@ -23,7 +23,6 @@
#include "plugins/ExtM3uPlaylistPlugin.hxx"
#include "plugins/M3uPlaylistPlugin.hxx"
#include "plugins/XspfPlaylistPlugin.hxx"
#include "plugins/DespotifyPlaylistPlugin.hxx"
#include "plugins/SoundCloudPlaylistPlugin.hxx"
#include "plugins/PlsPlaylistPlugin.hxx"
#include "plugins/AsxPlaylistPlugin.hxx"
@@ -54,9 +53,6 @@ const struct playlist_plugin *const playlist_plugins[] = {
&asx_playlist_plugin,
&rss_playlist_plugin,
#endif
#ifdef ENABLE_DESPOTIFY
&despotify_playlist_plugin,
#endif
#ifdef ENABLE_SOUNDCLOUD
&soundcloud_playlist_plugin,
#endif

@@ -43,12 +43,13 @@ playlist_provider_print(Client &client, const char *uri,
DetachedSong *song;
while ((song = e.NextSong()) != nullptr) {
if (playlist_check_translate_song(*song, base_uri.c_str(),
loader)) {
if (detail)
song_print_info(client, *song);
else
song_print_uri(client, *song);
}
loader) &&
detail)
song_print_info(client, *song);
else
/* fallback if no detail was requested or no
detail was available */
song_print_uri(client, *song);
delete song;
}

@@ -1,142 +0,0 @@
/*
* Copyright (C) 2003-2014 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 "DespotifyPlaylistPlugin.hxx"
#include "lib/despotify/DespotifyUtils.hxx"
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
#include "tag/Tag.hxx"
#include "DetachedSong.hxx"
#include "Log.hxx"
extern "C" {
#include <despotify.h>
}
#include <string.h>
#include <stdlib.h>
#include <string.h>
static void
add_song(std::forward_list<DetachedSong> &songs, ds_track &track)
{
const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
char uri[128];
char *ds_uri;
/* Create a spt://... URI for MPD */
snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
ds_uri = uri + strlen(dsp_scheme) + 3;
if (despotify_track_to_uri(&track, ds_uri) != ds_uri) {
/* Should never really fail, but let's be sure */
FormatDebug(despotify_domain,
"Can't add track %s", track.title);
return;
}
songs.emplace_front(uri, mpd_despotify_tag_from_track(track));
}
static bool
parse_track(struct despotify_session *session,
std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
struct ds_track *track = despotify_link_get_track(session, link);
if (track == nullptr)
return false;
add_song(songs, *track);
return true;
}
static bool
parse_playlist(struct despotify_session *session,
std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
ds_playlist *playlist = despotify_link_get_playlist(session, link);
if (playlist == nullptr)
return false;
for (ds_track *track = playlist->tracks; track != nullptr;
track = track->next)
add_song(songs, *track);
return true;
}
static SongEnumerator *
despotify_playlist_open_uri(const char *url,
gcc_unused Mutex &mutex, gcc_unused Cond &cond)
{
despotify_session *session = mpd_despotify_get_session();
if (session == nullptr)
return nullptr;
/* Get link without spt:// */
ds_link *link =
despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3);
if (link == nullptr) {
FormatDebug(despotify_domain, "Can't find %s\n", url);
return nullptr;
}
std::forward_list<DetachedSong> songs;
bool parse_result;
switch (link->type) {
case LINK_TYPE_TRACK:
parse_result = parse_track(session, songs, link);
break;
case LINK_TYPE_PLAYLIST:
parse_result = parse_playlist(session, songs, link);
break;
default:
parse_result = false;
break;
}
despotify_free_link(link);
if (!parse_result)
return nullptr;
songs.reverse();
return new MemorySongEnumerator(std::move(songs));
}
static const char *const despotify_schemes[] = {
"spt",
nullptr
};
const struct playlist_plugin despotify_playlist_plugin = {
"despotify",
nullptr,
nullptr,
despotify_playlist_open_uri,
nullptr,
despotify_schemes,
nullptr,
nullptr,
};

@@ -1,25 +0,0 @@
/*
* Copyright (C) 2003-2014 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_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
extern const struct playlist_plugin despotify_playlist_plugin;
#endif

@@ -178,7 +178,7 @@ const struct playlist_plugin embcue_playlist_plugin = {
embcue_playlist_open_uri,
nullptr,
nullptr,
embcue_playlist_suffixes,
nullptr,
nullptr,
};

@@ -25,14 +25,11 @@
#include "input/InputStream.hxx"
#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "lib/expat/ExpatParser.hxx"
#include "Log.hxx"
#include <string.h>
static constexpr Domain xspf_domain("xspf");
/**
* This is the state object for the GLib XML parser.
*/

@@ -92,7 +92,7 @@ check_range(Client &client, unsigned *value_r1, unsigned *value_r2,
return false;
}
if (unsigned(value) > std::numeric_limits<unsigned>::max()) {
if (value > std::numeric_limits<int>::max()) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -117,7 +117,7 @@ check_range(Client &client, unsigned *value_r1, unsigned *value_r2,
return false;
}
if (unsigned(value) > std::numeric_limits<unsigned>::max()) {
if (value > std::numeric_limits<int>::max()) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;

Some files were not shown because too many files have changed in this diff Show More