Compare commits

...

291 Commits

Author SHA1 Message Date
Max Kellermann
ae19bda1f2 release v0.21.12 2019-08-03 12:48:20 +02:00
Max Kellermann
f2d8fd769d player/Thread: don't restart unseekable song after failed seek attempt
The check IsSeekableCurrentSong() was added by commit
44b200240f in version 0.20.19, but it
caused a regression: by doing the branch only if the current song is
seekable, the player would restart the current song if it was not
seekable, and later the initial seek would fail; but we already know
it's not seekable, and so we should fail early.
2019-08-03 12:30:10 +02:00
Max Kellermann
9661062ae2 decoder/mad: pass const reference to RecoverFrameError() 2019-08-03 11:59:41 +02:00
Max Kellermann
2a07354cad decoder/mad: change integers to size_t 2019-08-03 11:44:02 +02:00
Max Kellermann
fc18fd571c decoder/mad: return from SynthAndSubmit() early 2019-08-03 11:42:05 +02:00
Max Kellermann
51abed9732 decoder/mad: pass mad_pcm to mad_fixed_to_24_buffer() 2019-08-03 11:40:06 +02:00
Max Kellermann
d00afc912c decoder/mad: eliminate the loop in SubmitPCM()
libmad has a hard-coded maximum PCM buffer size; if we make our
output_buffer just as large, we can avoid the loop, because any
possible size will fit.
2019-08-03 11:36:05 +02:00
Max Kellermann
9d0fe725eb decoder/mad: rename a few misnamed methods 2019-08-03 11:32:42 +02:00
Max Kellermann
8a432c9b7f decoder/mad: move code to LoadNextFrame() 2019-08-03 11:32:06 +02:00
Max Kellermann
187204f03c decoder/mad: move code to HandleCurrentFrame() 2019-08-03 11:32:06 +02:00
Max Kellermann
5e5fadb5f2 decoder/mad: remove unnecessary initializers
These will not be used until they are initialized in SyncAndSend().
2019-08-03 08:49:26 +02:00
Max Kellermann
952c793235 decoder/mad: subtract libmad decoder delay from LAME encoder padding
Apparently, libmad not only inserts 529 samples of silence at the
beginning of the file, but also removes them at the end.

This solves the last piece of
https://github.com/MusicPlayerDaemon/MPD/issues/601

Closes https://github.com/MusicPlayerDaemon/MPD/issues/601
2019-08-03 08:35:00 +02:00
Max Kellermann
3e3d8c7f9d decoder/mad: pad the input buffer with zero bytes and end of file
libmad requires padding the input buffer with "MAD_BUFFER_GUARD" zero
bytes at the end of the file, or else it is unable to decode the last
frame.

This fixes yet another bug which prevented this plugin from decoding
the last frame, see
https://github.com/MusicPlayerDaemon/MPD/issues/601
2019-08-03 08:32:27 +02:00
Max Kellermann
9b99a9897a decoder/mad: don't count the Xing/LAME metadata frame
The Xing/LAME frame indicates how many frames there are, but that
excludes the initial Xing/LAME frame.  Therefore, it should not be
counted.

This fixes an off-by-one bug which caused the last frame to be
skipped, fixing one part of
https://github.com/MusicPlayerDaemon/MPD/issues/601
2019-08-03 08:25:48 +02:00
Max Kellermann
4f56fdc397 decoder/mad: make "current_frame" zero-based
Increment "current_frame" after processing the frame.
2019-08-03 08:24:25 +02:00
Max Kellermann
c87d6825ec decoder/mad: add API documentation 2019-08-03 08:07:30 +02:00
Max Kellermann
00830a20e3 decoder/mad: convert to class, make almost everything private 2019-08-03 07:52:51 +02:00
Max Kellermann
d39d2874b4 decoder/mad: move code to methods RunDecoder(), RunScan() 2019-08-03 07:49:41 +02:00
Max Kellermann
a0a74951b8 decoder/mad: eliminate attribute "bit_rate"
This also fixes a bug which caused the bit rate to not update after
seeking.
2019-08-03 00:38:45 +02:00
Max Kellermann
779a6855ff decoder/mad: add noexcept 2019-08-03 00:28:59 +02:00
Max Kellermann
f7ed7446ae decoder/mad: use MAD_F_MIN and MAD_F_MAX 2019-08-03 00:27:59 +02:00
Max Kellermann
9d44a6d2ae decoder/mad: use Clamp() 2019-08-03 00:26:57 +02:00
Max Kellermann
10da9ee7ba decoder/mad: refactor local variables in FillBuffer() 2019-08-02 23:19:11 +02:00
Max Kellermann
f9eff31205 decoder/mad: use sizeof(input_buffer) 2019-08-02 23:19:11 +02:00
Max Kellermann
1d74a029a2 decoder/mad: simplify variable initialization in FillBuffer() 2019-08-02 23:19:11 +02:00
Max Kellermann
6b8ca514bb decoder/mad: fix broken log message
Broken since commit f8bfea8bae
2019-08-02 22:58:16 +02:00
Max Kellermann
f51e555154 decoder/mad: change "mp3_" suffix to "mad_" 2019-08-02 22:49:55 +02:00
Max Kellermann
61a3c69a06 decoder/mad: make enums strictly-typed 2019-08-02 22:49:55 +02:00
Max Kellermann
089615a01e decoder/mad: include cleanup 2019-08-02 22:49:55 +02:00
Max Kellermann
52bee8f81f util/StaticFifoBuffer: add GetAvailable() 2019-08-02 22:49:55 +02:00
Max Kellermann
adc25e648f util/StaticFifoBuffer: add constexpr 2019-08-02 22:49:33 +02:00
Max Kellermann
31da8eac9b util/StaticFifoBuffer: add noexcept 2019-08-02 22:49:05 +02:00
Max Kellermann
e00464435b util/Compiler.h: move compiler version checks to meson.build 2019-08-02 15:53:16 +02:00
Diomendius
b81138bda1 Fix JACK plugin outputting only to left channel
The JACK output plugin would not correctly upmix mono input files when exactly 2 output ports were configured. This fixes that.
2019-08-02 15:52:20 +02:00
Max Kellermann
6de088140b lib/xiph/OggVisitor: invoke OnOggPacket() with the "E_O_S" packet
The "end of stream" packet is not special; it contains normal data,
and thus we should pass it to OnOggPacket().

This fixes one part of https://github.com/MusicPlayerDaemon/MPD/issues/601
2019-08-02 14:04:08 +02:00
Max Kellermann
86d0534638 lib/xiph/OggVisitor: more API documentation 2019-08-02 13:56:00 +02:00
Max Kellermann
1033dbca2b playlist/Song: add missing includes 2019-07-29 11:31:30 +02:00
Max Kellermann
b955334882 decoder/opus: ignore case in replay gain tag names
Closes https://github.com/MusicPlayerDaemon/MPD/issues/604
2019-07-29 10:40:37 +02:00
Max Kellermann
90ea3bf985 playlist/Song: support backslash in relative URIs
Closes https://github.com/MusicPlayerDaemon/MPD/issues/607
2019-07-29 09:58:53 +02:00
Max Kellermann
83b0871248 test/test_translate_song: remove unused variable "s1" 2019-07-29 09:52:57 +02:00
Max Kellermann
d8aec4b2dc test/run_decoder: catch StopDecoder
This exception is usually thrown by class DecoderBridge, but the Opus
plugin (ab)uses it as well, so we need to catch it.
2019-07-12 17:49:12 +02:00
Max Kellermann
39b302dcad increment version number to 0.21.12 2019-07-12 17:22:20 +02:00
Max Kellermann
f6125f0c35 release v0.21.11 2019-07-03 15:16:27 +02:00
Max Kellermann
f780ac418a output/alsa: log when generating silence due to slow decoder
MPD used to do that when this code lived in the player thread, but it
was removed by commit 98a7c62d7a4f716d90af6d78e18d1a3b10bc54b3; and
the replacement code in the ALSA output plugin didn't have it.
2019-06-28 18:15:30 +02:00
Max Kellermann
61a72a5d13 output/alsa: schedule a timer to generate silence
Without this timer, DispatchSockets() may disable the
MultiSocketMonitor and if Play() doesn't get called soon, it never
gets a chance to generate silence.  However if Play() gets called,
generating silence isn't necessary anymore...

Resulting from this misdesign (added by commit ccafe3f3cf in 0.21.3),
the silence generator didn't work reliably.
2019-06-28 18:04:49 +02:00
Max Kellermann
0c0a354753 output/alsa: add a new flag "waiting" for xrun management
In DispatchSockets(), when there was not enough data, but enough for
current playback, the method would disable the "active" flag so the
next Play() call would re-enable the MultiSocketMonitor.

This was an abuse of the flag which could result in a crash
in Cancel(), because that method asserts that the period_buffer is
empty, which it may be not.

The solution is to add anther flag called "waiting" which shares some
behavior with the old flag.
2019-06-28 18:04:49 +02:00
Max Kellermann
3c5f860fb8 output/alsa: Cancel() also affects "active" (documentation) 2019-06-28 18:04:49 +02:00
Max Kellermann
3da1fa88d0 output/alsa: fix comment typo 2019-06-28 18:04:49 +02:00
Max Kellermann
fac15aaffb output/alsa: fix comment typo 2019-06-28 14:39:54 +02:00
Max Kellermann
c926021599 output/alsa: always redo DrainInternal() after writing
Draining isn't finished just because the period_buffer has run empty.
It is only finished after snd_pcm_drain() has succeeded.
2019-06-28 09:10:16 +02:00
Max Kellermann
543776d9c9 output/alsa: check PCM state before calling snd_pcm_drain()
Apparently, if snd_pcm_drain() returns EAGAIN, it does not actually
want to be called again; the next call will snd_pcm_drain() will also
return EAGAIN, forever, even though the PCM state has meanwhile
switched to SND_PCM_STATE_SETUP.  This causes a busy loop; to fix
this, we should always check snd_pcm_state() to see if draining is
really required.
2019-06-28 08:55:25 +02:00
Max Kellermann
8bf3f9b874 input/tidal: deprecated because Tidal has changed the protocol
See https://github.com/MusicPlayerDaemon/MPD/issues/545
2019-06-26 23:14:07 +02:00
Max Kellermann
f07f8f7d88 decoder/wildmidi: add fallbacks for libwildmidi<0.4
Fix build breakage from commit ea639269d8
2019-06-26 23:13:23 +02:00
Max Kellermann
39b40ac1fd decoder/wildmidi: remove unused variable wildmidi_domain 2019-06-26 23:10:20 +02:00
Max Kellermann
ea639269d8 decoder/wildmidi: throw PluginUnavailable on WildMidi_Init() error
Closes https://github.com/MusicPlayerDaemon/MPD/issues/589
2019-06-26 22:40:27 +02:00
Max Kellermann
0abaa3ecc5 decoder/wildmidi: throw PluginUnavailable if config file does not exist
This makes the configuration error more visible, possibly addressing
one part of https://github.com/MusicPlayerDaemon/MPD/issues/589
2019-06-26 22:38:40 +02:00
Max Kellermann
c4d3efe71d decoder/List: handle exception PluginUnavailable 2019-06-26 22:02:54 +02:00
Max Kellermann
85e82e3d4d decoder/List: annotate exceptions thrown by DecoderPlugin::Init() 2019-06-26 22:01:45 +02:00
Max Kellermann
f44011519c meson.build: increase protocol version to 0.21.11
Commit 1eae9339f2 added support for
multiple "groups" in the "list" command, and this change allows
clients to detect that this behavior, which had been documented for
several years, is now implemented properly.
2019-06-18 15:35:38 +02:00
Max Kellermann
2c3eeb7194 MusicChunk: pad MusicChunkInfo to a multiple of 8 bytes
Workaround for a regression caused by commit
a06bf388d9, revealing a problem with
discarding odd numer of frames in the DSD_U32 and DoP converters,
causing distortions with DSD_U32 and DoP on 32 bit CPUs.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/469
2019-06-17 21:24:32 +02:00
Max Kellermann
79839db3a3 output/oss: return early if PcmExport::Export() returns empty array
This can happen if the DoP converter doesn't get enough source samples
for one destination quad.  This isn't a critical bug, because the OSS
plugin doesn't support DoP yet, but it's good to be prepared.
2019-06-17 21:07:30 +02:00
Max Kellermann
d478bdda8e pcm/Export: document that Export() may return an empty buffer 2019-06-17 21:07:29 +02:00
Max Kellermann
1eae9339f2 db/Interface: CollectUniqueTags() allows multiple "groups"
Instead of passing tag and group, pass an array of tags.  To support a
nested return value, return a nested std::map of std::maps.  Each key
specifies the tag value, and each value may be another nesting level.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/408
2019-06-16 10:39:29 +02:00
Max Kellermann
923c1b6220 doc/include: remove obsolete DocBook fragment 2019-06-11 09:29:20 +02:00
Max Kellermann
09884e608b increment version number to 0.21.11 2019-06-11 09:29:05 +02:00
Max Kellermann
e239009295 release v0.21.10 2019-06-05 22:32:32 +02:00
Max Kellermann
3fae2150f5 decoder/OpusReader: return StringView
Since we now don't duplicate all items, we can easily remove the 64kB
limit from OpusReader::ReadString() and instead silently ignore and
skip all strings which are longer than 4 kB.

This fixes a tag duplication bug with Opus file containing a very long
`METADATA_BLOCK_PICTURE` tag, which occurred because the Opus plugin
returned false after parsing all tags, and then the MPD core fell back
to FFmpeg which scanned the tags again.
2019-06-05 22:19:35 +02:00
cathugger
f9ca2f52c1 output/httpd: reject some well-known request paths
Return `404 not found` for some common well-known paths, as clients requesting them usually do that automatically and don't expect endless audio stram.

Closes 
2019-06-05 21:53:46 +02:00
cathugger
4b81cf0c2c output/httpd: use strncmp instead of memcmp
memcmp use may result in out of bounds access
2019-06-05 21:53:46 +02:00
Max Kellermann
e7acbf112c output/httpd: fix indent 2019-06-05 21:53:43 +02:00
Max Kellermann
304d45b551 Revert "player/Thread: remove unnecessary "pipe" check"
This reverts commit ff3e2c0514.  The
check was necessary, after all, because this is what checked whether
the decoder had finished the current or the next song.

> The "queued" flag can only possibly be set if the decoder is still
> decoding the current song or if the decoder is stopped.

That was wrong because ProcessCommand() sets `queued=true` and also
starts the decoder (if it was idle).

> This is also what the following assert() checks.

That was also wrong, because the assert() has two conditions.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/566
2019-05-31 17:23:12 +02:00
Max Kellermann
0f488dcecf doc/protocol.rst: binary responses do have a newline after all
Closes https://github.com/MusicPlayerDaemon/MPD/issues/568
2019-05-31 16:47:41 +02:00
Max Kellermann
17039aec70 doc/user.rst: more heading corrections
According to http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections
2019-05-31 16:30:06 +02:00
Max Kellermann
fb6cb07912 doc/developer.rst: remove outdated section about the clang static analyzer 2019-05-31 16:27:43 +02:00
Max Kellermann
e9e0e02db3 doc/user.rst: use ".. note:" 2019-05-31 16:26:52 +02:00
Max Kellermann
03507037e8 increment version number to 0.21.10 2019-05-31 16:16:56 +02:00
Max Kellermann
66a8fac25e release v0.21.9 2019-05-20 17:10:58 +02:00
Max Kellermann
1b902e00b4 doc/protocol.rst: several clarifications
Closes https://github.com/MusicPlayerDaemon/MPD/issues/340
2019-05-20 17:06:20 +02:00
Max Kellermann
923e66738c player/Thread: fix "single" mode race condition
If the decoder finishes decoding the current song between the two
IsIdle() checks, MPD stops playback instead of starting the decoder
for the next song.

This is usually not visible problem, because the main thread restarts
it via playlist::ResumePlayback(), but that way it, ignores "single"
mode.

As a workaround, this commit adds another "queued" check which
re-enters the player loop and checks again whether to start the
decoder.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/556
2019-05-20 16:22:01 +02:00
Max Kellermann
ff3e2c0514 player/Thread: remove unnecessary "pipe" check
The "queued" flag can only possibly be set if the decoder is still
decoding the current song or if the decoder is stopped.  This is also
what the following assert() checks.  This check was not necessary.
2019-05-20 16:20:59 +02:00
Max Kellermann
6922a2f55e input/buffered: check error in IsAvailable() 2019-05-17 12:43:45 +02:00
Max Kellermann
ca5a400dbe input/buffered: rethrow read_error in Check() 2019-05-16 22:08:33 +02:00
Max Kellermann
63fe4d1d17 input/buffered: wake up client thread on seek error 2019-05-16 22:05:25 +02:00
Max Kellermann
ca06d9d3bf input/buffered: fix deadlock bug 2019-05-16 21:11:03 +02:00
Max Kellermann
ed2db04f43 doc/mpd.conf.5: remove ALSA specific documentation
ALSA is just one out of many output plugins, and detailed plugin
documentation should only live in the user manual, without having
duplicates in the (brief) manpage.

Also move "mixer_type" to the "optional audio output parameters"
section; it is a generic option, not specific to ALSA.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/552
2019-05-13 22:51:48 +02:00
Max Kellermann
de0afa0e08 doc/mpd.conf.5: fix section indent 2019-05-13 22:51:45 +02:00
Max Kellermann
f0d3227d7b doc/protocol.rst: add references to audio_output_format 2019-05-13 22:46:23 +02:00
Max Kellermann
fb07a7cecc doc/user.rst: move audio format spec to section "Global Audio Format" 2019-05-13 22:39:49 +02:00
Max Kellermann
c6b08a4d48 doc/user.rst: add reference to audio_output_format 2019-05-13 22:39:44 +02:00
Max Kellermann
040e87ad8d doc/user.rst: more markup 2019-05-13 22:36:19 +02:00
Max Kellermann
d5521ead56 doc/user.rst: add missing space 2019-05-13 22:36:19 +02:00
Max Kellermann
f8468451c9 android/AndroidManifest.xml: increment versionCode after hotfix upload 2019-05-04 13:25:05 +02:00
Max Kellermann
65df6ca14e android/Settings: request READ_EXTERNAL_STORAGE permission
Using this API function requires SDK level 23.
2019-05-04 07:29:41 +02:00
Max Kellermann
36dec47bf7 android/build.py: link ARMv7 binary with libunwind
Fixes nullptr dereference when an exception gets thrown because there
is no ".eh_frame" section for unwinding.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/543
2019-05-03 20:15:50 +02:00
Max Kellermann
478cedcadf increment version number to 0.21.9 2019-05-03 20:15:33 +02:00
Max Kellermann
cabcbb059d release v0.21.8 2019-04-23 14:35:14 +02:00
Max Kellermann
5e21b2db3c doc/protocol.rst: "list file" is deprecated
Closes https://github.com/MusicPlayerDaemon/MPD/issues/526
2019-04-23 14:29:42 +02:00
Max Kellermann
3a0d6d96c1 input/smbclient: wrap in MaybeBufferedInputStream
This enables the input buffer for remote files and caches file
contents in MPD.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/376
2019-04-23 14:08:27 +02:00
Max Kellermann
f39d2d33c0 python/build/libs.py: upgrade Boost to 1.70.0 2019-04-23 14:08:27 +02:00
Max Kellermann
ead3dc6a92 LocateUri: pass URI plugin kind, optionally disables plugin verify
Commit b3a458338a added a LocateUri()
call to several playlist commands, which applied InputPlugin URI
scheme verification to playlist URIs.  This broke the SoundCloud
playlist plugin which uses "soundcloud://" URIs for which no input
plugin exists.

This commit allows the caller to specify the kind of plugin which
shall be used to verify the URI.  Right now, only "input" is
implemented; "storage" uses the "input" verification for now; and
"playlist" has no verification at all (for now).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/528
2019-04-18 10:03:15 +02:00
Max Kellermann
7d814cc899 neighbor/smbclient: fix double smbc_closedir() call
There is already one call in ReadServers(), which is the correct place
to do it.
2019-04-18 09:40:56 +02:00
Max Kellermann
f5b4606c09 .travis.yml: switch to another PPA for a newer ninja version
Fixes Travis failure with Meson 0.50:

 ERROR: Could not detect Ninja v1.5 or newer
2019-04-18 09:40:30 +02:00
Max Kellermann
d6dbf64efb CommandLine: fix another build failure with -Ddatabase=false
Split several printf() calls to make it easier to deal with all those
#ifdefs.
2019-04-18 09:20:12 +02:00
Eugene Gorodinsky
8d18b4c24b Fix meson.build to work properly with '-Ddatabase=false' 2019-04-18 08:55:13 +02:00
Max Kellermann
fe8621906d systemd: add user socket unit
Copy the system socket unit to the "user" directory.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/530
2019-04-10 16:37:13 +02:00
Max Kellermann
b4fcbdb235 systemd/socket: use %t instead of hard-coding /run
This allows using the file as a user unit, where "%t" maps to
"$XDG_RUNTIME_DIR".

Proposed in https://github.com/MusicPlayerDaemon/MPD/issues/530
2019-04-10 16:34:40 +02:00
Max Kellermann
f4b5a28596 doc/protocol: mention that stickers are only implemented for songs
Closes https://github.com/MusicPlayerDaemon/MPD/issues/524
2019-04-10 16:33:17 +02:00
Max Kellermann
6cbd77fc57 doc/protocol.rst: mention "in seconds" where it was missing
Closes https://github.com/MusicPlayerDaemon/MPD/issues/523
2019-04-10 16:30:26 +02:00
cotko
1bc78e9f2c Fid move doc args 2019-04-10 13:16:58 +02:00
Max Kellermann
cb6282e0a7 doc/developer.rst: remove mailing list, refer to GitHub instead 2019-04-10 11:36:03 +02:00
Max Kellermann
f6941f9a44 event/SocketMonitor: don't cancel if OnSocketReady() returns false
Expect OnSocketReady() to cancel events.  If it returns false, the
SocketMonitor may be destructed already.  This fixes a use-after-free
bug in the "httpd" output plugin.
2019-04-04 10:24:58 +02:00
Max Kellermann
d2eb4df8fc event/{Fully,}BufferedSocket: add more API documentation 2019-04-04 10:24:58 +02:00
Max Kellermann
df33a898d7 zeroconf/Bonjour: fix OnSocketReady() return value
Keep the SocketMonitor registered.  This wrong return value was added
6 years ago in commit 72cf8dd8a0, andd
apparently, nobody ever noticed.
2019-04-04 10:24:29 +02:00
Max Kellermann
325c7b8e8b output/httpd: close client connection on error
This missing piece probably never really hurt, because
HttpdClient::OnSocketClosed() would be called right after a socket
error, but it's better to be explicit about closing on error.
2019-04-04 09:39:22 +02:00
Max Kellermann
380656d8c9 output/httpd: add missing mutex lock 2019-04-03 22:53:03 +02:00
Max Kellermann
9111bc2c21 output/httpd: add more API documentation about locking 2019-04-03 22:49:25 +02:00
Max Kellermann
37b54179d8 net/IPv[46]Address: add cast to void* to fix GCC9 build failure
Fixes:

 src/net/IPv4Address.hxx: In member function 'constexpr IPv4Address::operator SocketAddress() const':
 src/net/IPv4Address.hxx:171:24: error: a reinterpret_cast is not a constant expression
   171 |   return SocketAddress((const struct sockaddr *)&address,
       |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 src/net/IPv6Address.hxx: In member function 'constexpr IPv6Address::operator SocketAddress() const':
 src/net/IPv6Address.hxx:138:24: error: a reinterpret_cast is not a constant expression
   138 |   return SocketAddress((const struct sockaddr *)&address,
       |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Closes https://github.com/MusicPlayerDaemon/MPD/issues/522
2019-04-03 16:59:53 +02:00
Max Kellermann
511826763a increment version number to 0.21.8 2019-04-03 12:27:18 +02:00
Max Kellermann
ef10354d06 release v0.21.7 2019-04-03 12:18:29 +02:00
Max Kellermann
158458db5f python/build/libs.py: upgrade libnfs to 4.0.0 2019-04-03 11:37:33 +02:00
Max Kellermann
e183ab5cf8 python/build/libs.py: upgrade CURL to 7.64.1 2019-04-03 11:35:13 +02:00
Max Kellermann
fef839e2a9 python/build/libs.py: upgrade FFmpeg to 4.1.3 2019-04-03 11:34:32 +02:00
Max Kellermann
9776e43bbe android/AndroidManifest.xml: update version number 2019-04-03 11:28:59 +02:00
Max Kellermann
5201147ab1 input/curl: use std::throw_with_nested() instead of logging the exception
Let the caller decide what to do with the original exception.
2019-03-29 17:34:51 +01:00
Max Kellermann
fb7daa0d05 input/smbclient: use std::throw_with_nested() to construct PluginUnavailable
Preserve the original exception.
2019-03-29 17:32:23 +01:00
Max Kellermann
2e9f3d8b9f decoder/HybridDSD: downgrade log message to "debug"
This plugin is interesting only for a tiny fraction of MPD users, so
let's not spam everybody else's log with it.
2019-03-29 17:15:48 +01:00
Max Kellermann
976731ab6c command/playlist: invoke the RemoteTagScanner on all newly added songs
Closes https://github.com/MusicPlayerDaemon/MPD/issues/234
2019-03-29 17:01:31 +01:00
François Revol
0d8942e64a Haiku: remove redundant calls to delete_sem()
Fixes .

Semaphores are kernel-managed objects, calling delete_sem() twice is not more
dangerous than calling close() twice on an fd though, it would just return
an error.
2019-03-29 14:33:49 +01:00
François Revol
37a0f04712 Haiku: add version info to the resources like win32 does 2019-03-29 14:33:27 +01:00
François Revol
cde9348009 Haiku: fix adding resources
The custom_command was run in src/haiku/ and created a file with only resources inside.

Since xres edits the file in-place and meson doesn't like it, we have to run a shell script for now.
Maybe later I'll add proper support in meson.
2019-03-29 14:32:59 +01:00
François Revol
095e6e6ad4 Haiku: meson.build: fix linking (missing libs) 2019-03-29 14:32:19 +01:00
François Revol
9d0bf5e95c Haiku: fix build 2019-03-29 14:32:06 +01:00
Max Kellermann
8b327f1d9b filter/AutoConvert: implement Flush() 2019-03-24 22:42:06 +01:00
Max Kellermann
aef0507abb filter/Filter: fix typo in API doc 2019-03-24 22:34:11 +01:00
Max Kellermann
6bab3bcfea test/RunChromaprint: add missing override 2019-03-20 13:30:13 +01:00
Max Kellermann
a854595886 event/ServerSocket: runtime error if abstract sockets are unavailable 2019-03-20 13:09:16 +01:00
Max Kellermann
8fc3c5c612 event/ServerSocket: add HAVE_UN check to AddAbstract()
Closes https://github.com/MusicPlayerDaemon/MPD/issues/510
2019-03-20 13:06:09 +01:00
Max Kellermann
4f408bd952 event/ServerSocket, doc, ...: refer to AF_LOCAL as "local socket"
.. and not "UNIX domain socket.  Be consistent about the naming.
2019-03-20 12:57:26 +01:00
Max Kellermann
7de8fd04a4 doc/plugins.rst: add the Haiku plugin and mark it as unmaintained 2019-03-18 18:24:51 +01:00
Max Kellermann
8158bd218c doc/plugins.rst: add filter plugin reference 2019-03-18 18:05:18 +01:00
Max Kellermann
aa1d867b72 doc/user.rst: document the "filters" setting 2019-03-18 17:05:23 +01:00
Max Kellermann
34c8242133 doc/user.rst: add more links 2019-03-18 17:01:55 +01:00
Max Kellermann
e22bdee808 win32/res/meson.build: drop tilde suffix from version number before splitting
MPD sometimes uses version numbers like "0.22~git" to mark unreleased
versions.  That makes the win32 resource compiler unhappy, because it
expects numbers only.
2019-03-18 09:58:40 +01:00
Jörg Krause
7f87de783f src/lib/gcrypt/meson.build: use dependency() for quering linker flags
Since version 0.49.0 the Meson build system has native support for
finding and using the gcrypt library using the `dependency()` function.

`dependency()` has the advantage over `find_library()` as it queries the
required linker flags for proper linking with external libraries, e.g.
libgpg-error.

As the latest released version 1.8.4 of libgcrypt does not
provide a .pc file, using `libgcrypt-config` is the only way to query
the required linker flags.

Unfortunately, there is an issue when cross compiling mpd and the user does not
define `libgcrypt-config` in the cross file. If the user sets the qobuz feature
to `auto` and the target does not have libgcrypt installed, the Meson
build system will falsly assume libgcrypt is available for the target as
it uses the native `libgcrypt-config` on the host and pretend is has
found the library.

Therefore, we still rely on `find_library()` to workaround this buggy
behavior. This way, if qobuz feature detection is set to `auto`, the
feature is disabled in case there is no target libgcrypt available.

Fixes building mpd statically with the qobuz feature enabled. Otherwise
the build fails with undefined references because of the missing libgpg-error
dependency:

```
/sysroot/usr/lib/libgcrypt.a(libgcrypt_la-visibility.o): In function `gcry_strerror':
visibility.c:(.text+0x14): undefined reference to `gpg_strerror'
```
2019-03-18 09:12:19 +01:00
Jörg Krause
c66389a453 meson.build: require Meson 0.49.0
Meson 0.49.0 adds native support for `libgcrypt-config` which is
necessary for detecting libgcrypt dependencies, as the latest
version 1.8.4 of libgcrypt does not provide a .pc file.
2019-03-18 09:11:46 +01:00
Max Kellermann
b63c1a2144 increment version number to 0.21.7 2019-03-18 09:11:16 +01:00
Max Kellermann
808dd7cc54 release v0.21.6 2019-03-17 23:52:13 +01:00
Max Kellermann
62a129c18f PlaylistFile: ignore empty playlist names
Closes https://github.com/MusicPlayerDaemon/MPD/issues/465 and
https://github.com/MusicPlayerDaemon/MPD/pull/466
2019-03-17 23:46:36 +01:00
Max Kellermann
c18cd941aa lib/xiph: disable Tremor detection if libvorbis was found
And disable libvorbis detection if Tremor was explicitly enabled.

This fixes a crash bug caused by libvorbis/Tremor ABI conflict caused
by commit 4f7d52dbf2
2019-03-17 23:36:52 +01:00
Max Kellermann
6d12c22653 decoder/ogg: ignore the BOS packet after seek to the beginning of song
Previously, MPD would skip the current song after attempting to seek
to its beginnig, because that was a seek to offset 0.  At offset 0,
MPD will see the BOS packet again, which results in throwing
StopDecoder in MPDOpusDecoder::OnOggEnd().

Closes https://github.com/MusicPlayerDaemon/MPD/issues/470
2019-03-17 23:14:59 +01:00
Max Kellermann
b76d78e6ae output/sles: enable power saving mode 2019-03-17 18:04:40 +01:00
Jacob Vosmaer
0a6e484b1a output/plugins/OSXOutputPlugin: add boost meson dependency 2019-03-17 16:59:24 +01:00
Max Kellermann
0bb71f1f20 output/pulse: use pa_channel_map_init_extend() instead of _auto()
Unlike pa_channel_map_init_auto(), pa_channel_map_init_extend() does
not fail if there is no valid mapping for the given channel count, but
instead maps additional "AUX" channels.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/493
2019-03-16 14:03:10 +01:00
Max Kellermann
1aa7cdd602 decoder/opus: fix replay gain when there are no other tags
The `tag_builder.empty()` check was wrong for the SubmitReplayGain()
call.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/497
2019-03-16 13:55:19 +01:00
Max Kellermann
a4b8a0d801 doc/protocol.rst: clarify filter expressions with multiple tag values
Clarification for https://github.com/MusicPlayerDaemon/MPD/issues/505
2019-03-16 13:23:44 +01:00
Max Kellermann
3bf521d5ca song/TagSongFilter: apply negation properly to multiple tag values
The old implementation didn't make a lot of sense; the "!=" operator
was not actually the opposite of "==".

Closes https://github.com/MusicPlayerDaemon/MPD/issues/505
2019-03-16 13:23:02 +01:00
Max Kellermann
0acb55cde5 song/StringFilter: remove obsolete #if 2019-03-16 13:23:02 +01:00
Max Kellermann
6b89fd6100 song/StringFilter: make MatchWithoutNegation() public 2019-03-16 13:23:02 +01:00
Max Kellermann
52ce39dc3e test/TestSongFilter: unit test for song filters
A few of those tests fail due to bugs.
2019-03-16 13:23:02 +01:00
Max Kellermann
7a3e15d8e5 test/meson.build: add section for filter tests 2019-03-16 13:23:02 +01:00
Max Kellermann
cf66a60c60 test/MakeTag: add noexcept 2019-03-16 13:23:02 +01:00
Max Kellermann
9b26d451e4 test/MakeTag: remove static 2019-03-16 13:23:02 +01:00
Max Kellermann
137ffba1b4 test/test_translate_song: move MakeTag() to header 2019-03-16 13:23:02 +01:00
Max Kellermann
5c5dc1b7c0 meson.build: increase protocol version to 0.21.6
There is a minor new feature (commit 713c1f2ba9) and clients might be
interested in detecting it by the protocol version.
2019-03-16 13:23:02 +01:00
Max Kellermann
9e9418294a song/TagSongFilter: eliminate Match(TagItem) 2019-03-15 20:28:27 +01:00
Max Kellermann
b850eb74b7 song/TagSongFilter: add code comments 2019-03-15 19:54:29 +01:00
Max Kellermann
67d73a2aee song/TagSongFilter: improve lambda indent 2019-03-15 19:54:16 +01:00
Max Kellermann
fde9a470dd song/TagSongFilter: eliminate the std::fill_n() call 2019-03-15 19:35:58 +01:00
Max Kellermann
8d1f30e55b tag/Fallback: add API documentation 2019-03-15 19:23:10 +01:00
Max Kellermann
ddd2b60489 doc/protocol.rst: add missing operators to example expressions 2019-03-15 19:14:06 +01:00
Max Kellermann
8777737861 doc/protocol.rst: use double backticks for tag names 2019-03-15 19:11:30 +01:00
Max Kellermann
cb71f6dd04 doc/protocol.rst: clarify the meaning of the any tag type 2019-03-15 19:09:55 +01:00
Max Kellermann
1881b0e975 song/TagSongFilter: rename MatchNN() to Match()
The "NN" suffix used to mean "no negation", but that's not how it's
implemented today.
2019-03-15 19:06:56 +01:00
Max Kellermann
98b29f6d1c meson.build: remove the libwinpthread-1.dll dependency on Windows
Closes https://github.com/MusicPlayerDaemon/MPD/issues/507
2019-03-14 20:07:06 +01:00
Max Kellermann
59fdfd25cb command/database: fix "list" with filter expression
Disable the 0.11 compatibility mode if the only argument is a filter
expression.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/506
2019-03-14 19:50:09 +01:00
Max Kellermann
0d98677212 playlist/flac: copy the URI to fix use-after-free bug
Closes https://github.com/MusicPlayerDaemon/MPD/issues/508
2019-03-14 19:30:33 +01:00
Max Kellermann
38f0c16904 system/UniqueFileDescriptor: add CreatePipeNonBlock() 2019-02-27 23:30:56 +01:00
Max Kellermann
4fbf6b6c95 net/StaticSocketAddress: remove GetAddress() 2019-02-27 23:26:59 +01:00
Max Kellermann
1f8ff48168 net/StaticSocketAddress: add GetLocalRaw() 2019-02-27 23:26:00 +01:00
Max Kellermann
20b6e0d684 net/SocketDescriptor: add SetTcpUserTimeout() 2019-02-27 23:22:12 +01:00
Max Kellermann
713c1f2ba9 Merge branch 'feature/playlist' of git://github.com/miccoli/MPD 2019-02-27 13:49:22 +01:00
Stefano Miccoli
a149bc4c5d update protocol documentation for new semantics of playlist abs. name 2019-02-26 00:12:09 +01:00
Stefano Miccoli
b3a458338a allow loading playlists specified as absolute filesystem paths
implement for the "load" command the same logic used for the "add"
command: local clients can load playlist specified as absolute paths.

For relative paths the old logic is preserved: first look for a stored
playlist, then look in the music directory.
2019-02-26 00:12:09 +01:00
Max Kellermann
44422b2b2f event/ServerSocket, config/Net: abstract socket support 2019-02-25 13:08:33 +01:00
Max Kellermann
f10afd38b5 NEWS: mention the cdio_paranoia build failure fix 2019-02-25 13:08:33 +01:00
Thomas Zander
4c50a5e0b3 Ensure SEEK_SET is set on systems where stdio.h is not pulled in by accident. 2019-02-23 18:04:00 +01:00
Max Kellermann
f255a485b7 increment version number to 0.21.6 2019-02-22 15:28:03 +01:00
Max Kellermann
1930d5774d release v0.21.5 2019-02-22 15:23:33 +01:00
Max Kellermann
7220a76be0 doc/plugins.rst: document udisks2/policykit rule 2019-02-22 15:22:20 +01:00
Max Kellermann
83f7610dd1 storage/udisks2: move empty string check out of the fallback block in MapUTF8()
Even if the LocalStorage is available, return the "udisks://" URI when
the MapUTF8() parameter is an empty string.  This fixes the mount URI
in the state file.
2019-02-22 15:07:40 +01:00
Max Kellermann
30e0644722 db/simple: call ReturnSong() on mounted database
Fixes a memory leak, or an assertion failure in the debug build.
2019-02-22 14:52:13 +01:00
Max Kellermann
3ada464020 db/simple: use C++11 initializer 2019-02-22 14:52:01 +01:00
Max Kellermann
d5983dd362 storage/udisks2: use the relative path
Closes 
2019-02-22 14:41:56 +01:00
Max Kellermann
98258acc37 storage/udisks2: pass Path to SetMountPoint() 2019-02-22 14:41:56 +01:00
Max Kellermann
8002bc752f NEWS: mention the udisks2 AlreadyMounted fix 2019-02-22 14:41:56 +01:00
Max Kellermann
834ad7a58f TagPrint: omit tags which were disabled by the client
Closes 
2019-02-22 13:05:38 +01:00
Max Kellermann
e8f2f98048 tag/Mask: fix another typo, this time in operator^=
Similar to commit ff1ff1e54a
2019-02-22 12:44:36 +01:00
Max Kellermann
c672b60d07 build/pkg-config.sh: add comment 2019-02-22 12:39:59 +01:00
Max Kellermann
ea269c9c92 python/build/libs.py: upgrade CURL to 7.64.0 2019-02-22 12:10:06 +01:00
Max Kellermann
1fe3a77640 python/build/libs.py: upgrade FFmpeg to 4.1.1 2019-02-22 12:09:35 +01:00
Max Kellermann
bbaeea1ab7 storage/udisks2: use existing mount point if already mounted
Fixes the "org.freedesktop.UDisks2.Error.AlreadyMounted" error.

Closes 
2019-02-21 13:32:03 +01:00
Max Kellermann
0a3aee9d82 storage/udisks2: move code to SetMountPoint() 2019-02-21 13:31:59 +01:00
Max Kellermann
2434020971 storage/udisks2: adjust lambda indent 2019-02-21 13:31:57 +01:00
Max Kellermann
41e0eb7378 lib/dbus/udisks2: parse the MountPoints property 2019-02-21 13:28:26 +01:00
Max Kellermann
6adf964c81 lib/dbus/ReadIter: add dbus_message_iter_get_fixed_array() wrapper 2019-02-21 12:56:05 +01:00
Max Kellermann
b59f37bc0a db/simple/Directory: close the Database in destructor
Fixes assertion failure.
2019-02-20 22:50:15 +01:00
Max Kellermann
cf2d171ccc db/simple: reorder checks in assert() to fix assertion failure
`light_song.Get()` could cause an assertion failure because the
`Manual<>` object must not be used if uninitialized.

Regression by commit ebc006ab52
2019-02-20 21:24:01 +01:00
Max Kellermann
cc28a7b67f Main: create Database on stack, move to Instance after Open() succeeded
This fixes use-after-free bug in SimpleDatabase::Close(), accessing
the `root` object which was already freed by the `catch` block in
Open().

By having the Database on the stack first, we can avoid calling
Close() on the failed-to-open Database from Instance's destructor.

Closes 
2019-02-20 20:50:28 +01:00
Max Kellermann
8b5c33cecd Instance: use std::unique_ptr<> to manage the Database pointer 2019-02-20 20:48:20 +01:00
Max Kellermann
6c28adbcd2 db/Plugin: use std::unique_ptr<> to manage Database pointers 2019-02-20 20:43:31 +01:00
Max Kellermann
2125e3ed57 db/simple/Directory: add noexcept 2019-02-20 20:39:49 +01:00
Max Kellermann
3da7ecfadf mixer/pulse: add missing ParseFloat() check 2019-02-20 19:27:13 +01:00
Max Kellermann
5bb02bbd39 mixer/pulse: move volume_scale_factor up to improve struct packing 2019-02-20 19:25:55 +01:00
Max Kellermann
f11aa09f7c mixer/pulse: add const to volume_scale_factor 2019-02-20 19:25:53 +01:00
Max Kellermann
02eb4752d3 mixer/pulse: use C++11 initializer 2019-02-20 19:25:45 +01:00
Max Kellermann
d9c3215584 mixer/pulse: rename scale to scale_volume
Make it less generic, to avoid clashes.
2019-02-20 19:23:11 +01:00
Clément Pit-Claudel
110e6d026b mixer/pulse: Add a new 'scale' parameter to allow volumes above 100
Closes GH-479.
2019-02-17 16:14:52 -05:00
Max Kellermann
c0f57b8a8b net/IPv[46]Address: update copyright 2019-02-19 13:00:45 +01:00
Max Kellermann
57633fbcb3 net/AllocatedSocketAddress: add methods IsV6Any(), IsV4Mapped() 2019-02-19 12:51:24 +01:00
Max Kellermann
864c87e6c0 net/SocketAddress: add method GetLocalPath() 2019-02-19 12:50:40 +01:00
Max Kellermann
1a516cf3c0 net/AllocatedSocketAddress: add method GetLocalRaw() 2019-02-19 12:43:16 +01:00
Max Kellermann
5c25499c5e lib/cdio/Paranoia: add method GetDiscSectorRange() 2019-02-19 12:40:36 +01:00
Max Kellermann
da4bb4c298 fs/io/OutputStream: update include guard 2019-02-19 12:39:29 +01:00
Max Kellermann
5b8ff61799 fs/io/BufferedOutputStream: add WithBufferedOutputStream() 2019-02-19 12:37:53 +01:00
Max Kellermann
56bded07b1 system/UniqueFileDescriptor: import std::swap 2019-02-19 12:36:54 +01:00
Max Kellermann
db144a43ad system/Open: add OpenWriteOnly(), OpenDirectory() 2019-02-19 12:16:41 +01:00
Max Kellermann
5965f62b56 system/EpollFD: include cleanup 2019-02-19 11:51:52 +01:00
Max Kellermann
05aa9f72a9 util/StringView: add SkipPrefix(), RemoveSuffix() 2019-02-19 11:51:32 +01:00
Max Kellermann
281461f0f0 nfs: work around assertion failure on exception during program init
Closes 
2019-02-15 18:33:58 +01:00
Max Kellermann
f70eb63879 Instance: eliminate FinishShutdownUpdate(), move code to destructor 2019-02-15 18:20:11 +01:00
Max Kellermann
99c23cf139 Instance: eliminate ShutdownDatabase(), move code to destructor
Destruct automatically, even if leaving the scope due to exception
being thrown.
2019-02-15 18:04:23 +01:00
Max Kellermann
9aa75e738c Merge branch 'protocol-doc-typo' of git://github.com/mxjeff/MPD 2019-02-15 18:03:49 +01:00
Max Kellermann
e9c45a9140 playlist/Registry: add RAII class 2019-02-05 23:03:29 +01:00
Max Kellermann
a065c6e6b9 Main: use AtScopeExit() to call DeinitFS() 2019-02-05 23:02:50 +01:00
Max Kellermann
feb5ff9bd2 Mapper: remove empty function mapper_finish() 2019-02-05 23:01:09 +01:00
Max Kellermann
92ec3f0881 valgrind.suppressions: add GObject/libgcrypt/libsmbclient suppressions 2019-02-05 22:53:02 +01:00
Max Kellermann
98c47d9d36 Instance: remove FinishShutdownPartitions()
The list of partitions is cleared automatically.
2019-02-05 22:53:02 +01:00
Max Kellermann
6c67408944 event/Loop: add flag alive
This replaces the old `dead` flag which was unreliable; it was `false`
if the EventThread was not yet started, which could cause deadlocks in
BlockingCall().
2019-02-05 22:38:45 +01:00
Max Kellermann
261a816b21 command/AllCommands: remove empty function command_finish() 2019-02-05 22:15:41 +01:00
Max Kellermann
7a23c123c8 decoder/List: add RAII class 2019-02-05 22:12:22 +01:00
Max Kellermann
e85b24bee0 decoder/List: add noexcept 2019-02-05 22:11:51 +01:00
Max Kellermann
9e73ea77b4 input/Init: add RAII class 2019-02-05 22:07:49 +01:00
Max Kellermann
b0739eca87 test/ConfigGlue: merge duplicate code from various debug programs 2019-02-05 21:56:20 +01:00
Max Kellermann
848f6aa5ab Main: stop io_thread and rtio_thread automatically
They will be stopped by ~EventThread() when the `Instance` is deleted.
2019-02-05 21:49:59 +01:00
Max Kellermann
c9ba4f3f9c archive/List: add RAII class 2019-02-05 21:40:07 +01:00
Max Kellermann
c0e9246a66 archive/List: add noexcept 2019-02-05 21:38:46 +01:00
Max Kellermann
096c23f27d unix/SignalHandlers: add RAII class 2019-02-05 21:36:51 +01:00
Max Kellermann
40bde1eac9 unix/SignalHandlers: add noexcept 2019-02-05 21:36:35 +01:00
Max Kellermann
4b55ed17a9 LogInit: add noexcept 2019-02-05 21:36:35 +01:00
kaliko
4f757a5add Fixed protocol documentation
* "lsinfo" argument is optional
 * "tagtypes disable" arguments are mandatory (typo)
2019-02-03 10:38:34 +01:00
Max Kellermann
674c137e5f NEWS: mention the TagMask typo fix 2019-02-02 15:17:25 +01:00
kaliko
ff1ff1e54a Fixed typo in TagMask 2019-02-02 15:14:31 +01:00
Yue Wang
42b22187c8 [OSXOutput] Throw an error when device not found
Currently it falls back to system default device (either internal speaker or headphone) when device not found. 
I believe it is a better to fail in this case, to make it better aligned with platforms (such as alsa).
2019-01-25 19:50:27 -08:00
Max Kellermann
cfe22502ab fs/io/StdioOutputStream: add noexcept 2019-01-22 09:03:49 +01:00
Max Kellermann
d77b0c7dcd net/SocketAddress: add constexpr 2019-01-22 08:42:35 +01:00
Max Kellermann
5cf889b676 util/WStringView: add missing include 2019-01-22 08:38:03 +01:00
Max Kellermann
ffc36d5255 input/buffered: implement seeking to end of file
Previously, a seek to the end of the file would cause an assertion
failure in SparseMap::Check() because the given offset was invalid.

Closes 
2019-01-22 07:42:00 +01:00
Max Kellermann
0126276e2f FileCommands: log irregular errors while looking for cover art 2019-01-21 22:21:11 +01:00
Max Kellermann
58d6ddab9e FileCommands: catch all exceptions 2019-01-21 22:19:32 +01:00
Max Kellermann
05db6934eb FileCommands: fix deadlock in "albumart" command
Must lock the mutex before calling any of the unprotected InputStream methods.

Closes 
2019-01-21 22:16:46 +01:00
Max Kellermann
02c68c5cdb net/HostParser: add noexcept 2019-01-21 21:20:43 +01:00
Max Kellermann
b02fee7309 util/PrintException: support "const char *" 2019-01-21 21:19:35 +01:00
Max Kellermann
424f75c9e1 util/OffsetPointer: remove redundant inline keywords from constexpr functions 2019-01-21 21:19:09 +01:00
Max Kellermann
f6e1176f97 util/CharUtil: remove redundant inline keywords from constexpr functions 2019-01-21 21:18:23 +01:00
Max Kellermann
e4700c0a27 util/Cast: remove redundant inline keywords from constexpr functions 2019-01-21 21:17:58 +01:00
Max Kellermann
cf23fd8774 fs/io/FileOutputStream: add constructor with directory fd 2019-01-21 21:10:02 +01:00
Max Kellermann
dee8872395 fs/io/FileOutputStream: move code to Open() 2019-01-21 21:09:34 +01:00
Max Kellermann
4ba9357a9c input/CdioParanoia: C++ wrappers for libcdio types 2019-01-21 20:20:20 +01:00
Max Kellermann
48ec09ab1e test/net/TestIPv4Address: make literal unsigned to work around -Wsign-compare 2019-01-21 14:39:24 +01:00
Max Kellermann
754f4048a8 output/shout: evaluate tls option only if TLS is enabled in libshout
Fixes build failure after commit
0cea67ee70
2019-01-21 14:36:43 +01:00
Max Kellermann
037bb07d08 db/VHelper: include DetachedSong.hxx to fix GCC 9 build failure
GCC 9's libstdc++ is unable to use forward-declared types as
std::vector item because the compiler wants to resolve `noexcept()` on
the item destructor.
2019-01-21 14:34:12 +01:00
Max Kellermann
87635c5268 input/CdioParanoia: use the new function names 2019-01-21 14:18:55 +01:00
Max Kellermann
528b4338f4 input/CdioParanoia: use cdio_cddap_free_messages() on recent library versions 2019-01-21 14:16:51 +01:00
Max Kellermann
c780b8bba9 input/CdioParanoia: remove useless cdda_messages() call 2019-01-21 12:36:59 +01:00
Max Kellermann
ca34f3250b input/CdioParanoia: detect libcdio version at compile time
libcdio_paranoia was split from libcdio in version 90, and at the same
time, the header was moved from cdio/paranoia.h to
cdio/paranoia/paranoia.h.  We can easily detect this version at
compile time which is faster than configure time.
2019-01-21 12:14:13 +01:00
Max Kellermann
6a68e1c3f3 test/net/TestIPv6Address: work around failure on macOS 2019-01-21 12:13:52 +01:00
Max Kellermann
85f77ec81d test/net/TestLocalSocketAddress: can't use strcmp() if the string isn't null-terminated. 2019-01-21 12:12:36 +01:00
Max Kellermann
37debed0b8 python/build/libs.py: upgrade Boost to 1.69.0 2019-01-21 10:19:46 +01:00
Max Kellermann
008383f24a python/build/libs.py: upgrade CURL to 7.63.0 2019-01-21 10:11:50 +01:00
Jörg Krause
4f7d52dbf2 meson: add fixed-point Vorbis (Tremor) decoder support
Re-add build support for the fixed-point Vorbis (Tremor) decoder, which
was dropped when switching from Autotools to Meson.

Note, that it is not possible to build both, the Vorbis and the Tremor
decoder.

Closes: 
2019-01-21 08:35:17 +01:00
Max Kellermann
c7848da8f2 input/CdioParanoia: add const to pointer 2019-01-20 22:03:49 +01:00
Max Kellermann
10a6c5c57d input/CdioParanoia: make variables more local 2019-01-20 21:59:57 +01:00
Max Kellermann
2cc2bab309 test/net: new unit tests 2019-01-20 21:05:21 +01:00
Max Kellermann
701fd1d939 net/IPv4Address: fix comment typo 2019-01-20 21:05:12 +01:00
Max Kellermann
d1bdea8edb Merge branch 'shout_tls' of git://github.com/JakobOvrum/MPD 2019-01-20 21:03:42 +01:00
Jakob Ovrum
0cea67ee70 shout output plugin: add support for TLS 2019-01-19 17:36:14 +01:00
Thomas Klausner
3a0480a482 Add missing include of stdlib.h.
Closes https://github.com/MusicPlayerDaemon/MPD/issues/456
2019-01-15 16:52:40 +01:00
Max Kellermann
1fa99da3c2 net/IPv[46]Address: make the initializers even more portable
Similar to 5a5229b499: use more C++14
constexpr.
2019-01-14 19:21:07 +01:00
James D. Smith
22d669da18 Add APE mapping for album artist.
"De-facto" field mappings are available at http://wiki.hydrogenaud.io/index.php?title=Tag_Mapping.
2019-01-14 19:15:42 +01:00
Thomas Zander
772681f23d Fix link_args for mDNSResponder on non-darwin platforms 2019-01-13 14:09:14 +01:00
Max Kellermann
1862a98a44 increment version number to 0.21.5 2019-01-04 19:31:07 +01:00
208 changed files with 3304 additions and 1561 deletions
.travis.ymlNEWS
android
build
doc
meson.buildmeson_options.txt
python/build
src
CommandLine.cxxInstance.cxxInstance.hxxLocateUri.cxxLocateUri.hxxLogInit.cxxLogInit.hxxMain.cxxMapper.cxxMapper.hxxMusicChunk.hxxPlaylistFile.cxxSongLoader.cxxStateFile.cxxStats.cxxTagPrint.cxx
archive
command
config
db
decoder
event
filter
fs
haiku
input
lib
mixer
neighbor
net
output
pcm
player
playlist
song
storage
system
tag
thread
unix
util
zeroconf
systemd
test
valgrind.suppressions
win32/res

@@ -9,7 +9,7 @@ matrix:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:mhier/libboost-latest'
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- g++-6
@@ -34,7 +34,7 @@ matrix:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:mhier/libboost-latest'
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- g++-8

108
NEWS

@@ -1,3 +1,111 @@
ver 0.21.12 (2019/08/03)
* decoder
- mad: update bit rate after seeking
- mad: fix several bugs preventing the plugin from decoding the last frame
- opus: ignore case in replay gain tag names
- opus, vorbis: decode the "end of stream" packet
* output
- jack: fix mono-to-stereo conversion
* player
- don't restart unseekable song after failed seek attempt
* Windows
- support backslash in relative URIs loaded from playlists
ver 0.21.11 (2019/07/03)
* input
- tidal: deprecated because Tidal has changed the protocol
* decoder
- wildmidi: log error if library initialization fails
* output
- alsa: fix busy loop while draining
- alsa: fix missing drain call
- alsa: improve xrun-avoiding silence generator
- alsa: log when generating silence due to slow decoder
- alsa, osx: fix distortions with DSD_U32 and DoP on 32 bit CPUs
* protocol
- fix "list" with multiple "group" levels
ver 0.21.10 (2019/06/05)
* decoder
- opus: fix duplicate tags
* output
- httpd: reject some well-known URIs
* fix crash bug (0.21.9 regression)
ver 0.21.9 (2019/05/20)
* input
- buffer: fix deadlock bug
* Android
- fix crash on ARMv7
- request storage permission on Android 6+
* fix spurious "single" mode bug
ver 0.21.8 (2019/04/23)
* input
- smbclient: download to buffer instead of throttling transfer
* output
- httpd: add missing mutex lock
- httpd: fix use-after-free bug
* playlist
- soundcloud: fix "Unsupported URI scheme" (0.21.6 regression)
* fix Bonjour bug
* fix build failure with GCC 9
* fix build failure with -Ddatabase=false
* systemd: add user socket unit
* doc: "list file" is deprecated
ver 0.21.7 (2019/04/03)
* input
- qobuz/tidal: scan tags when loading a playlist
* require Meson 0.49.0 for native libgcrypt-config support
* fix build failure with -Dlocal_socket=false
* Haiku
- fix build
- add version info
ver 0.21.6 (2019/03/17)
* protocol
- allow loading playlists specified as absolute filesystem paths
- fix negated filter expressions with multiple tag values
- fix "list" with filter expression
- omit empty playlist names in "listplaylists"
* input
- cdio_paranoia: fix build failure due to missing #include
* decoder
- opus: fix replay gain when there are no other tags
- opus: fix seeking to beginning of song
- vorbis: fix Tremor conflict resulting in crash
* output
- pulse: work around error with unusual channel count
- osx: fix build failure
* playlist
- flac: fix use-after-free bug
* support abstract sockets on Linux
* Windows
- remove the unused libwinpthread-1.dll dependency
* Android
- enable SLES power saving mode
ver 0.21.5 (2019/02/22)
* protocol
- fix deadlock in "albumart" command
- fix "tagtypes disable" command
* database
- simple: fix assertion failure
- fix assertion failures with mount points
* storage
- udisks: fix "AlreadyMounted" error
- udisks: use relative path from mount URI
- fix memory leak
* input
- buffer: fix crash bug when playing remote WAV file
* tags
- ape: map "Album Artist"
* output
- shout: add support for TLS
* mixer
- pulse: add "scale_volume" setting
ver 0.21.4 (2019/01/04)
* database
- inotify: fix crash bug "terminate called after throwing ..."

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="26"
android:versionName="0.21.4">
android:versionCode="35"
android:versionName="0.21.12">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>

@@ -138,6 +138,12 @@ class AndroidNdkToolchain:
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
libstdcxx_libs = '-lc++_static -lc++abi'
if self.is_armv7:
# On 32 bit ARM, clang generates no ".eh_frame" section;
# instead, the LLVM unwinder library is used for unwinding
# the stack after a C++ exception was thrown
libstdcxx_libs += ' -lunwind'
if use_cxx:
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags

@@ -6,7 +6,7 @@ android_sdk = get_option('android_sdk')
android_abi = get_option('android_abi')
android_sdk_build_tools_version = '27.0.0'
android_sdk_platform = 'android-21'
android_sdk_platform = 'android-23'
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)

@@ -21,10 +21,12 @@ package org.musicpd;
import java.util.LinkedList;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -178,6 +180,14 @@ public class Settings extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
/* TODO: this sure is the wrong place to request
permissions - it will cause MPD to quit
immediately; we should request permissions when we
need them, but implementing that is complicated, so
for now, we do it here to give users a quick
solution for the problem */
requestAllPermissions();
setContentView(R.layout.settings);
mRunButton = (ToggleButton) findViewById(R.id.run);
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
@@ -203,6 +213,31 @@ public class Settings extends Activity {
super.onCreate(savedInstanceState);
}
private void checkRequestPermission(String permission) {
if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED)
return;
try {
this.requestPermissions(new String[]{permission}, 0);
} catch (Exception e) {
Log.e(TAG, "requestPermissions(" + permission + ") failed",
e);
}
}
private void requestAllPermissions() {
if (android.os.Build.VERSION.SDK_INT < 23)
/* we don't need to request permissions on
this old Android version */
return;
/* starting with Android 6.0, we need to explicitly
request all permissions before using them;
mentioning them in the manifest is not enough */
checkRequestPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
}
private void connectClient() {
mClient = new Main.Client(this, new Main.Client.Callback() {

@@ -1,5 +1,9 @@
#!/bin/sh -e
# This is a wrapper for pkg-config which helps with cross-compiling;
# it sets up environment variables to pkg-config searches for
# libraries in the sysroot where a copy of this script is located.
BIN=`dirname $0`
ROOT=`dirname "$BIN"`

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.4'
version = '0.21.12'
# The full version, including alpha/beta/rc tags.
release = version

@@ -2,12 +2,12 @@ Developer's Manual
##################
Introduction
============
************
This is a guide for those who wish to hack on the MPD source code. MPD is an open project, and we are always happy about contributions. So far, more than 150 people have contributed patches. This document is work in progress. Most of it may be incomplete yet. Please help!
Code Style
==========
**********
* indent with tabs (width 8)
* don't write CPP when you can write C++: use inline functions and constexpr instead of macros
@@ -18,7 +18,6 @@ Code Style
* classes and functions names use CamelCase; variables are lower-case with words separated by underscore
Some example code:
~~~~~~~~~~~~~~~~~~
.. code-block:: c
@@ -33,7 +32,7 @@ Some example code:
}
Hacking The Source
==================
******************
MPD sources are managed in a git repository on
`Github <https://github.com/MusicPlayerDaemon/>`_.
@@ -59,7 +58,7 @@ possible, to be sure that you don't break any disabled code.
Don't mix several changes in one single patch. Create a separate patch for every change. Tools like :program:`stgit` help you with that. This way, we can review your patches more easily, and we can pick the patches we like most first.
Basic stgit usage
-----------------
=================
stgit allows you to create a set of patches and refine all of them: you can go back to any patch at any time, and re-edit it (both the code and the commit message). You can reorder patches and insert new patches at any position. It encourages creating separate patches for tiny changes.
@@ -94,35 +93,7 @@ When the whole patch series is finished, convert stgit patches to git commits:
stg commit
Submitting Patches
==================
******************
Send your patches to the mailing list:
Email: `mpd-devel <mpd-devel@musicpd.org>`_
:program:`git pull` requests are preferred.
Development Tools
=================
Clang Static Analyzer
---------------------
The `static analyzer <http://clang-analyzer.llvm.org/>`_ is a tool that helps find bugs. To run it on the MPD code base, install LLVM and clang. configure MPD to use clang:
.. code-block:: sh
./configure --enable-debug CXX=clang++ CC=clang ...
It is recommended to use :code:`--enable-debug`, because the analyzer
takes advantage of :dfn:`assert()` calls, which are only enabled in
the debug build.
Now run the analyzer:
.. code-block:: sh
scan-build --use-c++=clang++ --use-cc=clang make
The options :code:`--use-c++` and :code:`--use-cc` are necessary
because it invokes :command:`cc` for actually compiling the sources by
default. That breaks, because MPD requires a C99 compiler.
Submit pull requests on GitHub:
https://github.com/MusicPlayerDaemon/MPD/pulls

@@ -1,165 +0,0 @@
<?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 decimal 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 decimal disc number in a multi-disc
album.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_artistid</varname>: the artist id in the
<ulink
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_albumid</varname>: the album id in the
<ulink
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_albumartistid</varname>: the album artist
id in the <ulink
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_trackid</varname>: the track id in the
<ulink
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_releasetrackid</varname>: the release track
id in the <ulink
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
<listitem>
<para>
<varname>musicbrainz_workid</varname>: the work id in the
<ulink
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
</itemizedlist>

@@ -140,7 +140,6 @@ of database.
.B auto_update_depth <N>
Limit the depth of the directories being watched, 0 means only watch
the music directory itself. There is no limit by default.
.TP
.SH REQUIRED AUDIO OUTPUT PARAMETERS
.TP
.B type <type>
@@ -164,57 +163,12 @@ Specifies how replay gain is applied. The default is "software",
which uses an internal software volume control. "mixer" uses the
configured (hardware) mixer control. "none" disables replay gain on
this audio output.
.SH OPTIONAL ALSA OUTPUT PARAMETERS
.TP
.B device <dev>
This specifies the device to use for audio output. The default is "default".
.TP
.B mixer_type <hardware, software or none>
Specifies which mixer should be used for this audio output: the
hardware mixer (available for ALSA, OSS and PulseAudio), the software
mixer or no mixer ("none"). By default, the hardware mixer is used
for devices which support it, and none for the others.
.TP
.B mixer_device <mixer dev>
This specifies which mixer to use. The default is "default". To use
the second sound card in a system, use "hw:1".
.TP
.B mixer_control <mixer ctrl>
This specifies which mixer control to use (sometimes referred to as
the "device"). The default is "PCM". Use "amixer scontrols" to see
the list of possible controls.
.TP
.B mixer_index <mixer index>
A number identifying the index of the named mixer control. This is
probably only useful if your alsa device has more than one
identically\-named mixer control. The default is "0". Use "amixer
scontrols" to see the list of controls with their indexes.
.TP
.B auto_resample <yes or no>
Setting this to "no" disables ALSA's software resampling, if the
hardware does not support a specific sample rate. This lets MPD do
the resampling. "yes" is the default and allows ALSA to resample.
.TP
.B auto_channels <yes or no>
Setting this to "no" disables ALSA's channel conversion, if the
hardware does not support a specific number of channels. Default: "yes".
.TP
.B auto_format <yes or no>
Setting this to "no" disables ALSA's sample format conversion, if the
hardware does not support a specific sample format. Default: "yes".
.TP
.B buffer_time <time in microseconds>
This sets the length of the hardware sample buffer in microseconds. Increasing
it may help to reduce or eliminate skipping on certain setups. Most users do
not need to change this. The default is 500000 microseconds (0.5 seconds).
.TP
.B period_time <time in microseconds>
This sets the time between hardware sample transfers in microseconds.
Increasing this can reduce CPU usage while lowering it can reduce underrun
errors on bandwidth-limited devices. Some users have reported good results
with this set to 50000, but not all devices support values this high. Most
users do not need to change this. The default is 256000000 / sample_rate(kHz),
or 5804 microseconds for CD-quality audio.
.SH FILES
.TP
.BI ~/.mpdconf

@@ -4,10 +4,10 @@ Plugin reference
.. _database_plugins:
Database plugins
----------------
================
simple
~~~~~~
------
The default plugin. Stores a copy of the database in memory. A file is used for permanent storage.
@@ -25,7 +25,7 @@ The default plugin. Stores a copy of the database in memory. A file is used for
- Compress the database file using gzip? Enabled by default (if built with zlib).
proxy
~~~~~
-----
Provides access to the database of another :program:`MPD` instance using libmpdclient. This is useful when you run mount the music directory via NFS/SMB, and the file server already runs a :program:`MPD` instance. Only the file server needs to update the database.
@@ -45,30 +45,30 @@ Provides access to the database of another :program:`MPD` instance using libmpdc
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expensive of a very small amount of additional network traffic. Disabled by default.
upnp
~~~~
----
Provides access to UPnP media servers.
Storage plugins
---------------
===============
local
~~~~~
-----
The default plugin which gives :program:`MPD` access to local files. It is used when music_directory refers to a local directory.
curl
~~~~
----
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
smbclient
~~~~~~~~~
---------
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
nfs
~~~
---
Load music files from a NFS server. It is used when :code:`music_directory` contains a nfs:// URI according to RFC2224, for example :samp:`nfs://servername/path`.
@@ -81,38 +81,55 @@ This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD`
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
udisks
~~~~~~
------
Mount file systems (e.g. USB sticks or other removable media) using
the udisks2 daemon via D-Bus. To obtain a valid udisks2 URI, consult
:ref:`the according neighbor plugin <neighbor_plugin>`.
It might be necessary to grant :program:`MPD` privileges to control
:program:`udisks2` through :program:`policykit`. To do this, create a
file called :file:`/usr/share/polkit-1/rules.d/mpd-udisks.rules` with
the following text::
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.udisks2.filesystem-mount" ||
action.id == "org.freedesktop.udisks2.filesystem-mount-other-seat") &&
subject.user == "mpd") {
return polkit.Result.YES;
}
});
If you run MPD as a different user, change ``mpd`` to the name of your
MPD user.
.. _neighbor_plugin:
Neighbor plugins
----------------
================
smbclient
~~~~~~~~~
---------
Provides a list of SMB/CIFS servers on the local network.
udisks
~~~~~~
------
Queries the udisks2 daemon via D-Bus and obtain a list of file systems (e.g. USB sticks or other removable media).
upnp
~~~~
----
Provides a list of UPnP servers on the local network.
.. _input_plugins:
Input plugins
-------------
=============
alsa
~~~~
----
Allows :program:`MPD` on Linux to play audio directly from a soundcard using the scheme alsa://. Audio is formatted as 44.1 kHz 16-bit stereo (CD format). Examples:
@@ -125,7 +142,7 @@ Allows :program:`MPD` on Linux to play audio directly from a soundcard using the
mpc add alsa://hw:1,0 plays audio from device hw:1,0 cdio_paranoia
cdio_paranoia
~~~~~~~~~~~~~
-------------
Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]". The simplest form cdda:// plays the whole disc in the default drive.
@@ -141,7 +158,7 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
curl
~~~~
----
Opens remote files or streams over HTTP using libcurl.
@@ -163,22 +180,22 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
- Verify the certificate's name against host? `More information <http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html>`_.
ffmpeg
~~~~~~
------
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
file
~~~~
----
Opens local files
mms
~~~
---
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
nfs
~~~
---
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
@@ -189,7 +206,7 @@ Allows :program:`MPD` to access files on NFSv3 servers without actually mounting
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
smbclient
~~~~~~~~~
---------
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
@@ -198,7 +215,7 @@ Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microso
mpc add smb://servername/sharename/filename.ogg
qobuz
~~~~~
-----
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
@@ -224,10 +241,15 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
- The `Qobuz format identifier <https://github.com/Qobuz/api-documentation/blob/master/endpoints/track/getFileUrl.md#parameters>`_, i.e. a number which chooses the format and quality to be requested from Qobuz. The default is "5" (320 kbit/s MP3).
tidal
~~~~~
-----
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
.. warning::
This plugin is currently defunct because Tidal has changed the
protocol and decided not to share documentation.
.. code-block:: none
mpc add tidal://track/59727857
@@ -250,10 +272,10 @@ Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. I
.. _decoder_plugins:
Decoder plugins
---------------
===============
adplug
~~~~~~
------
Decodes AdLib files using libadplug.
@@ -267,17 +289,17 @@ Decodes AdLib files using libadplug.
- The sample rate that shall be synthesized by the plugin. Defaults to 48000.
audiofile
~~~~~~~~~
---------
Decodes WAV and AIFF files using libaudiofile.
faad
~~~~
----
Decodes AAC files using libfaad.
ffmpeg
~~~~~~
------
Decodes various codecs using FFmpeg.
@@ -293,12 +315,12 @@ Decodes various codecs using FFmpeg.
- Sets the FFmpeg muxer option probesize, which specifies probing size in bytes, i.e. the size of the data to analyze to get stream information. The `FFmpeg formats documentation <https://ffmpeg.org/ffmpeg-formats.html>`_ has more information.
flac
~~~~
----
Decodes FLAC files using libFLAC.
dsdiff
~~~~~~
------
Decodes DFF files containing DSDIFF data (e.g. SACD rips).
@@ -312,12 +334,12 @@ Decodes DFF files containing DSDIFF data (e.g. SACD rips).
- Decode the least significant bit first. Default is no.
dsf
~~~
---
Decodes DSF files containing DSDIFF data (e.g. SACD rips).
fluidsynth
~~~~~~~~~~
----------
MIDI decoder based on `FluidSynth <http://www.fluidsynth.org/>`_.
@@ -333,7 +355,7 @@ MIDI decoder based on `FluidSynth <http://www.fluidsynth.org/>`_.
- The absolute path of the soundfont file. Defaults to :file:`/usr/share/sounds/sf2/FluidR3_GM.sf2`.
gme
~~~
---
Video game music file emulator based on `game-music-emu <https://bitbucket.org/mpyne/game-music-emu/wiki/Home>`_.
@@ -347,7 +369,7 @@ Video game music file emulator based on `game-music-emu <https://bitbucket.org/m
- Enable more accurate sound emulation.
hybrid_dsd
~~~~~~~~~~
----------
`Hybrid-DSD
<http://dsdmaster.blogspot.de/p/bitperfect-introduces-hybrid-dsd-file.html>`_
@@ -370,12 +392,12 @@ of the file is better.
- This specifies whether to support gapless playback of MP3s which have the necessary headers. Useful if your MP3s have headers with incorrect information. If you have such MP3s, it is highly recommended that you fix them using `vbrfix <http://www.willwap.co.uk/Programs/vbrfix.php>`_ instead of disabling gapless MP3 playback. The default is to support gapless MP3 playback.
mad
~~~
---
Decodes MP3 files using `libmad <http://www.underbit.com/products/mad/>`_.
mikmod
~~~~~~
------
Module player based on `MikMod <http://mikmod.sourceforge.net/>`_.
@@ -391,7 +413,7 @@ Module player based on `MikMod <http://mikmod.sourceforge.net/>`_.
- Sets the sample rate generated by libmikmod. Default is 44100.
modplug
~~~~~~~
-------
Module player based on MODPlug.
@@ -405,27 +427,27 @@ Module player based on MODPlug.
- Number of times to loop the module if it uses backward loops. Default is 0 which prevents looping. -1 loops forever.
mpcdec
~~~~~~
------
Decodes Musepack files using `libmpcdec <http://www.musepack.net/>`_.
mpg123
~~~~~~
------
Decodes MP3 files using `libmpg123 <http://www.mpg123.de/>`_.
opus
~~~~
----
Decodes Opus files using `libopus <http://www.opus-codec.org/>`_.
pcm
~~~
---
Read raw PCM samples. It understands the "audio/L16" MIME type with parameters "rate" and "channels" according to RFC 2586. It also understands the MPD-specific MIME type "audio/x-mpd-float".
sidplay
~~~~~~~
-------
C64 SID decoder based on `libsidplayfp <https://sourceforge.net/projects/sidplay-residfp/>`_ or `libsidplay2 <https://sourceforge.net/projects/sidplay2/>`_.
@@ -447,23 +469,23 @@ C64 SID decoder based on `libsidplayfp <https://sourceforge.net/projects/sidplay
- Only libsidplayfp. Absolute path to basic rom image file.
sndfile
~~~~~~~
-------
Decodes WAV and AIFF files using `libsndfile <http://www.mega-nerd.com/libsndfile/>`_.
vorbis
~~~~~~
------
Decodes Ogg-Vorbis files using `libvorbis <http://www.xiph.org/ogg/vorbis/>`_.
wavpack
~~~~~~~
-------
Decodes WavPack files using `libwavpack <http://www.wavpack.com/>`_.
wildmidi
~~~~~~~~
--------
MIDI decoder based on `libwildmidi <http://www.mindwerks.net/projects/wildmidi/>`_.
@@ -479,10 +501,11 @@ MIDI decoder based on `libwildmidi <http://www.mindwerks.net/projects/wildmidi/>
.. _encoder_plugins:
Encoder plugins
---------------
===============
flac
~~~~
----
Encodes into `FLAC <https://xiph.org/flac/>`_ (lossless).
.. list-table::
@@ -495,7 +518,7 @@ Encodes into `FLAC <https://xiph.org/flac/>`_ (lossless).
- Sets the libFLAC compression level. The levels range from 0 (fastest, least compression) to 8 (slowest, most compression).
lame
~~~~
----
Encodes into MP3 using the `LAME <http://lame.sourceforge.net/>`_ library.
@@ -511,12 +534,12 @@ Encodes into MP3 using the `LAME <http://lame.sourceforge.net/>`_ library.
- Sets the bit rate in kilobit per second. Cannot be used with quality.
null
~~~~
----
Does not encode anything, passes the input PCM data as-is.
shine
~~~~~
-----
Encodes into MP3 using the `Shine <https://github.com/savonet/shine>`_ library.
@@ -530,7 +553,7 @@ Encodes into MP3 using the `Shine <https://github.com/savonet/shine>`_ library.
- Sets the bit rate in kilobit per second.
twolame
~~~~~~~
-------
Encodes into MP2 using the `TwoLAME <http://www.twolame.org/>`_ library.
@@ -546,7 +569,7 @@ Encodes into MP2 using the `TwoLAME <http://www.twolame.org/>`_ library.
- Sets the bit rate in kilobit per second. Cannot be used with quality.
opus
~~~~
----
Encodes into `Ogg Opus <http://www.opus-codec.org/>`_.
@@ -568,7 +591,7 @@ Encodes into `Ogg Opus <http://www.opus-codec.org/>`_.
.. _vorbis_plugin:
vorbis
~~~~~~
------
Encodes into `Ogg Vorbis <http://www.vorbis.com/>`_.
@@ -584,13 +607,13 @@ Encodes into `Ogg Vorbis <http://www.vorbis.com/>`_.
- Sets the bit rate in kilobit per second. Cannot be used with quality.
wave
~~~~
----
Encodes into WAV (lossless).
.. _resampler_plugins:
Resampler plugins
-----------------
=================
The resampler can be configured in a block named resampler, for example:
@@ -613,12 +636,12 @@ The following table lists the resampler options valid for all plugins:
- The name of the plugin.
internal
~~~~~~~~
--------
A resampler built into :program:`MPD`. Its quality is very poor, but its CPU usage is low. This is the fallback if :program:`MPD` was compiled without an external resampler.
libsamplerate
~~~~~~~~~~~~~
-------------
A resampler using `libsamplerate <http://www.mega-nerd.com/SRC/>`_ a.k.a. Secret Rabbit Code (SRC).
@@ -651,7 +674,7 @@ The following converter types are provided by libsamplerate:
- Linear interpolator, very fast, poor quality.
soxr
~~~~
----
A resampler using `libsoxr <http://sourceforge.net/projects/soxr/>`_, the SoX Resampler library
@@ -674,13 +697,15 @@ Valid quality values for libsoxr:
* "low"
* "quick"
.. _output_plugins:
Output plugins
--------------
==============
.. _alsa_plugin:
alsa
~~~~
----
The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ plugin uses libasound. It is recommended if you are using Linux.
@@ -739,7 +764,7 @@ The following attributes can be configured at runtime using the outputset comman
ao
~~
--
The ao plugin uses the portable `libao <https://www.xiph.org/ao/>`_ library. Use only if there is no native plugin for your operating system.
.. list-table::
@@ -756,7 +781,8 @@ The ao plugin uses the portable `libao <https://www.xiph.org/ao/>`_ library. Use
- This specifies how many bytes to write to the audio device at once. This parameter is to work around a bug in older versions of libao on sound cards with very small buffers. The default is 1024.
sndio
~~~~~
-----
The sndio plugin uses the `sndio <http://www.sndio.org/>`_ library. It should normally be used on OpenBSD.
.. list-table::
@@ -771,7 +797,7 @@ The sndio plugin uses the `sndio <http://www.sndio.org/>`_ library. It should no
- Set the application buffer time in milliseconds.
fifo
~~~~
----
The fifo plugin writes raw PCM data to a FIFO (First In, First Out) file. The data can be read by another program.
@@ -784,8 +810,18 @@ The fifo plugin writes raw PCM data to a FIFO (First In, First Out) file. The da
* - **path P**
- This specifies the path of the FIFO to write to. Must be an absolute path. If the path does not exist, it will be created when MPD is started, and removed when MPD is stopped. The FIFO will be created with the same user and group as MPD is running as. Default permissions can be modified by using the builtin shell command umask. If a FIFO already exists at the specified path it will be reused, and will not be removed when MPD is stopped. You can use the "mkfifo" command to create this, and then you may modify the permissions to your liking.
haiku
-----
Use the SoundPlayer API on the Haiku operating system.
This plugin is unmaintained and contains known bugs. It will be
removed soon, unless there is a new maintainer.
jack
~~~~
----
The jack plugin connects to a `JACK server <http://jackaudio.org/>`_.
.. list-table::
@@ -808,7 +844,8 @@ The jack plugin connects to a `JACK server <http://jackaudio.org/>`_.
- Sets the size of the ring buffer for each channel. Do not configure this value unless you know what you're doing.
httpd
~~~~~
-----
The httpd plugin creates a HTTP server, similar to `ShoutCast <http://www.shoutcast.com/>`_ / `IceCast <http://icecast.org/>`_. HTTP streaming clients like mplayer, VLC, and mpv can connect to it.
It is highly recommended to configure a fixed format, because a stream cannot switch its audio format on-the-fly when the song changes.
@@ -822,14 +859,15 @@ It is highly recommended to configure a fixed format, because a stream cannot sw
* - **port P**
- Binds the HTTP server to the specified port.
* - **bind_to_address ADDR**
- Binds the HTTP server to the specified address (IPv4, IPv6 or UNIX socket). Multiple addresses in parallel are not supported.
- Binds the HTTP server to the specified address (IPv4, IPv6 or local socket). Multiple addresses in parallel are not supported.
* - **encoder NAME**
- Chooses an encoder plugin. A list of encoder plugins can be found in the encoder plugin reference :ref:`encoder_plugins`.
* - **max_clients MC**
- Sets a limit, number of concurrent clients. When set to 0 no limit will apply.
null
~~~~
----
The null plugin does nothing. It discards everything sent to it.
.. list-table::
@@ -844,7 +882,8 @@ The null plugin does nothing. It discards everything sent to it.
.. _oss_plugin:
oss
~~~
---
The "Open Sound System" plugin is supported on most Unix platforms.
On Linux, OSS has been superseded by ALSA. Use the ALSA output plugin :ref:`alsa_plugin` instead of this one on Linux.
@@ -872,7 +911,7 @@ The according hardware mixer plugin understands the following settings:
- Choose a mixer control, defaulting to PCM.
openal
~~~~~~
------
The "OpenAL" plugin uses `libopenal <http://kcat.strangesoft.net/openal.html>`_. It is supported on many platforms. Use only if there is no native plugin for your operating system.
.. list-table::
@@ -885,7 +924,7 @@ The "OpenAL" plugin uses `libopenal <http://kcat.strangesoft.net/openal.html>`_.
- Sets the device which should be used. This can be any valid OpenAL device name. If not specified, then libopenal will choose a default device.
osx
~~~
---
The "Mac OS X" plugin uses Apple's CoreAudio API.
.. list-table::
@@ -906,7 +945,7 @@ The "Mac OS X" plugin uses Apple's CoreAudio API.
The channel map may not refer to outputs that do not exist according to the format. If the format is "*:*:1" (mono) and you have a four-channel sound card then "-1,-1,0,0" (dual mono output on the second pair of sound card outputs) is a valid channel map but "-1,-1,0,1" is not because the second channel ('1') does not exist when the output is mono.
pipe
~~~~
----
The pipe plugin starts a program and writes raw PCM data into its standard input.
@@ -922,7 +961,7 @@ The pipe plugin starts a program and writes raw PCM data into its standard input
.. _pulse_plugin:
pulse
~~~~~
-----
The pulse plugin connects to a `PulseAudio <http://www.freedesktop.org/wiki/Software/PulseAudio/>`_ server. Requires libpulse.
.. list-table::
@@ -935,9 +974,11 @@ The pulse plugin connects to a `PulseAudio <http://www.freedesktop.org/wiki/Soft
- Sets the host name of the PulseAudio server. By default, :program:`MPD` connects to the local PulseAudio server.
* - **sink NAME**
- Specifies the name of the PulseAudio sink :program:`MPD` should play on.
* - **scale_volume FACTOR**
- Specifies a linear scaling coefficient (ranging from 0.5 to 5.0) to apply when adjusting volume through :program:`MPD`. For example, chosing a factor equal to ``"0.7"`` means that setting the volume to 100 in :program:`MPD` will set the PulseAudio volume to 70%, and a factor equal to ``"3.5"`` means that volume 100 in :program:`MPD` corresponds to a 350% PulseAudio volume.
recorder
~~~~~~~~
--------
The recorder plugin writes the audio played by :program:`MPD` to a file. This may be useful for recording radio streams.
.. list-table::
@@ -949,13 +990,13 @@ The recorder plugin writes the audio played by :program:`MPD` to a file. This ma
* - **path P**
- Write to this file.
* - **format_path P**
- An alternative to path which provides a format string referring to tag values. The special tag iso8601 emits the current date and time in `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ format (UTC). Every time a new song starts or a new tag gets received from a radio station, a new file is opened. If the format does not render a file name, nothing is recorded. A tag name enclosed in percent signs ('%') is replaced with the tag value. Example: :file:`~/.mpd/recorder/%artist% - %title%.ogg`. Square brackets can be used to group a substring. If none of the tags referred in the group can be found, the whole group is omitted. Example: [~/.mpd/recorder/[%artist% - ]%title%.ogg] (this omits the dash when no artist tag exists; if title also doesn't exist, no file is written). The operators "|" (logical "or") and "&" (logical "and") can be used to select portions of the format string depending on the existing tag values. Example: ~/.mpd/recorder/[%title%|%name%].ogg (use the "name" tag if no title exists)
- An alternative to path which provides a format string referring to tag values. The special tag iso8601 emits the current date and time in `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ format (UTC). Every time a new song starts or a new tag gets received from a radio station, a new file is opened. If the format does not render a file name, nothing is recorded. A tag name enclosed in percent signs ('%') is replaced with the tag value. Example: :file:`-/.mpd/recorder/%artist% - %title%.ogg`. Square brackets can be used to group a substring. If none of the tags referred in the group can be found, the whole group is omitted. Example: [-/.mpd/recorder/[%artist% - ]%title%.ogg] (this omits the dash when no artist tag exists; if title also doesn't exist, no file is written). The operators "|" (logical "or") and "&" (logical "and") can be used to select portions of the format string depending on the existing tag values. Example: -/.mpd/recorder/[%title%|%name%].ogg (use the "name" tag if no title exists)
* - **encoder NAME**
- Chooses an encoder plugin. A list of encoder plugins can be found in the encoder plugin reference :ref:`encoder_plugins`.
shout
~~~~~
-----
The shout plugin connects to a ShoutCast or IceCast server using libshout. It forwards tags to this server.
You must set a format.
@@ -974,6 +1015,8 @@ You must set a format.
- Set the timeout for the shout connection in seconds. Defaults to 2 seconds.
* - **protocol icecast2|icecast1|shoutcast**
- Specifies the protocol that wil be used to connect to the server. The default is "icecast2".
* - **tls disabled|auto|auto_no_plain|rfc2818|rfc2817**
- Specifies what kind of TLS to use. The default is "disabled" (no TLS).
* - **mount URI**
- Mounts the :program:`MPD` stream in the specified URI.
* - **user USERNAME**
@@ -997,7 +1040,7 @@ You must set a format.
.. _sles_output:
sles
~~~~
----
Plugin using the `OpenSL ES <https://www.khronos.org/opensles/>`__
audio API. Its primary use is local playback on Android, where
@@ -1005,7 +1048,7 @@ audio API. Its primary use is local playback on Android, where
solaris
~~~~~~~
-------
The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
.. list-table::
@@ -1017,46 +1060,80 @@ The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
* - **device PATH**
- Sets the path of the audio device, defaults to /dev/audio.
.. _filter_plugins:
Filter plugins
==============
normalize
---------
Normalize the volume during playback (at the expensve of quality).
null
----
A no-op filter. Audio data is returned as-is.
route
-----
Reroute channels.
.. list-table::
:widths: 20 80
:header-rows: 1
* - Setting
- Description
* - **routes "0>0, 1>1, ..."**
- Specifies the channel mapping.
.. _playlist_plugins:
Playlist plugins
----------------
================
asx
~~~
---
Reads .asx playlist files.
cue
~~~
---
Reads .cue files.
embcue
~~~~~~
------
Reads CUE sheets from the "CUESHEET" tag of song files.
m3u
~~~
---
Reads .m3u playlist files.
extm3u
~~~~~~
------
Reads extended .m3u playlist files.
flac
~~~~
----
Reads the cuesheet metablock from a FLAC file.
pls
~~~
---
Reads .pls playlist files.
rss
~~~
---
Reads music links from .rss files.
soundcloud
~~~~~~~~~~
----------
Download playlist from SoundCloud. It accepts URIs starting with soundcloud://.
.. list-table::
@@ -1069,5 +1146,5 @@ Download playlist from SoundCloud. It accepts URIs starting with soundcloud://.
- An API key to access the SoundCloud servers.
xspf
~~~~
----
Reads XSPF playlist files.

@@ -14,6 +14,9 @@ Once the client is connected to the server, they conduct a
conversation until the client closes the connection. The
conversation flow is always initiated by the client.
All data between the client and the server is encoded in
UTF-8.
The client transmits a command sequence, terminated by the
newline character ``\n``. The server will
respond with one or more lines, the last of which will be a
@@ -42,9 +45,6 @@ quotation marks.
Argument strings are separated from the command and any other
arguments by linear white-space (' ' or '\\t').
All data between the client and the server is encoded in
UTF-8.
Responses
=========
@@ -52,6 +52,28 @@ A command returns ``OK`` on completion or
``ACK some error`` on failure. These
denote the end of command execution.
Some commands return more data before the response ends with ``OK``.
Each line is usually in the form ``NAME: VALUE``. Example::
foo: bar
OK
.. _binary:
Binary Responses
----------------
Some commands can return binary data. This is initiated by a line
containing ``binary: 1234`` (followed as usual by a newline). After
that, the specified number of bytes of binary data follows, then a
newline, and finally the ``OK`` line. Example::
foo: bar
binary: 42
<42 bytes>
OK
Failure responses
-----------------
@@ -112,9 +134,9 @@ list begins with `command_list_begin` or
`command_list_ok_begin` and ends with
`command_list_end`.
It does not execute any commands until the list has ended.
The return value is whatever the return for a list of commands
is. On success for all commands,
It does not execute any commands until the list has ended. The
response is a concatentation of all individual responses.
On success for all commands,
``OK`` is returned. If a command
fails, no more commands are executed and the appropriate
``ACK`` error is returned. If
@@ -144,15 +166,20 @@ syntax::
``EXPRESSION`` is a string enclosed in parantheses which can be one
of:
- ``(TAG == 'VALUE')``: match a tag value.
``(TAG != 'VALUE')``: mismatch a tag value.
The special tag "*any*" checks all
tag values.
*albumartist* looks for
- ``(TAG == 'VALUE')``: match a tag value; if there are multiple
values of the given type, at least one must match.
``(TAG != 'VALUE')``: mismatch a tag value; if there are multiple
values of the given type, none of them must match.
The special tag ``any`` checks all
tag types.
``AlbumArtist`` looks for
``VALUE`` in ``AlbumArtist``
and falls back to ``Artist`` tags if
``AlbumArtist`` does not exist.
``VALUE`` is what to find.
An empty value string means: match only if the given tag type does
not exist at all; this implies that negation with an empty value
checks for the existence of the given tag type.
- ``(TAG contains 'VALUE')`` checks if the given value is a substring
of the tag value.
@@ -173,12 +200,13 @@ of:
file's time stamp with the given value (ISO 8601 or UNIX
time stamp).
- ``(AudioFormat == 'SAMPLERATE:BITS:CHANNELS')``:
compares the audio format with the given value.
- ``(AudioFormat == 'SAMPLERATE:BITS:CHANNELS')``: compares the audio
format with the given value. See :ref:`audio_output_format` for a
detailed explanation.
- ``(AudioFormat =~ 'SAMPLERATE:BITS:CHANNELS')``:
matches the audio format with the given mask (i.e. one
or more attributes may be "*").
or more attributes may be ``*``).
- ``(!EXPRESSION)``: negate an expression. Note that each expression
must be enclosed in parantheses, e.g. :code:`(!(artist == 'VALUE'))`
@@ -207,11 +235,11 @@ backslash.
Example expression which matches an artist named ``foo'bar"``::
(artist "foo\'bar\"")
(Artist == "foo\'bar\"")
At the protocol level, the command must look like this::
find "(artist \"foo\\'bar\\\"\")"
find "(Artist == \"foo\\'bar\\\"\")"
The double quotes enclosing the artist name must be escaped because
they are inside a double-quoted ``find`` parameter. The single quote
@@ -409,15 +437,18 @@ Querying :program:`MPD`'s status
- ``songid``: playlist songid of the current song stopped on or playing
- ``nextsong`` [#since_0_15]_: playlist song number of the next song to be played
- ``nextsongid`` [#since_0_15]_: playlist songid of the next song to be played
- ``time``: total time elapsed (of current playing/paused song)
- ``time``: total time elapsed (of current playing/paused song) in seconds
(deprecated, use ``elapsed`` instead)
- ``elapsed`` [#since_0_16]_: Total time elapsed within the current song, but with higher resolution.
- ``elapsed`` [#since_0_16]_: Total time elapsed within the
current song in seconds, but with higher resolution.
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
- ``bitrate``: instantaneous bitrate in kbps
- ``xfade``: ``crossfade`` in seconds
- ``mixrampdb``: ``mixramp`` threshold in dB
- ``mixrampdelay``: ``mixrampdelay`` in seconds
- ``audio``: The format emitted by the decoder plugin during playback, format: ``*samplerate:bits:channels*``. Check the user manual for a detailed explanation.
- ``audio``: The format emitted by the decoder plugin during
playback, format: ``samplerate:bits:channels``. See
:ref:`audio_output_format` for a detailed explanation.
- ``updating_db``: ``job id``
- ``error``: if there is an error, returns message here
@@ -432,7 +463,7 @@ Querying :program:`MPD`'s status
- ``albums``: number of albums
- ``songs``: number of songs
- ``uptime``: daemon uptime in seconds
- ``db_playtime``: sum of all song times in the db
- ``db_playtime``: sum of all song times in the database in seconds
- ``db_update``: last db update in UNIX time
- ``playtime``: time length of music played
@@ -594,7 +625,7 @@ Whenever possible, ids should be used.
Deletes the song ``SONGID`` from the
playlist
:command:`move {FROM} [{START:END} | {TO}]`
:command:`move [{FROM} | {START:END}] {TO}`
Moves the song at ``FROM`` or range of songs
at ``START:END`` [#since_0_15]_ to ``TO``
in the playlist.
@@ -714,7 +745,9 @@ and without the `.m3u` suffix).
Some of the commands described in this section can be used to
run playlist plugins instead of the hard-coded simple
`m3u` parser. They can access playlists in
the music directory (relative path including the suffix) or
the music directory (relative path including the suffix),
playlists in arbitrary location (absolute path including the suffix;
allowed only for clients that are connected via local socket), or
remote playlists (absolute URI with a supported scheme).
:command:`listplaylist {NAME}`
@@ -783,7 +816,7 @@ The music database
Returns the file size and actual number
of bytes read at the requested offset, followed
by the chunk requested as raw bytes, then a
by the chunk requested as raw bytes (see :ref:`binary`), then a
newline and the completion code.
Example::
@@ -791,8 +824,7 @@ The music database
albumart
size: 1024768
binary: 8192
<8192 bytes>
OK
<8192 bytes>OK
:command:`count {FILTER} [group {GROUPTYPE}]`
Count the number of songs and their total playtime in
@@ -853,8 +885,7 @@ The music database
:command:`list {TYPE} {FILTER} [group {GROUPTYPE}]`
Lists unique tags values of the specified type.
``TYPE`` can be any tag supported by
:program:`MPD` or
*file*.
:program:`MPD`.
Additional arguments may specify a :ref:`filter <filter_syntax>`.
The *group* keyword may be used
@@ -865,6 +896,10 @@ The music database
list album group albumartist
``list file`` was implemented in an early :program:`MPD` version,
but does not appear to make a lot of sense. It still works (to
avoid breaking compatibility), but is deprecated.
.. _command_listall:
:command:`listall [URI]`
@@ -909,7 +944,7 @@ The music database
.. _command_lsinfo:
:command:`lsinfo {URI}`
:command:`lsinfo [URI]`
Lists the contents of the directory
``URI``. The response contains records
starting with ``file``,
@@ -924,7 +959,7 @@ The music database
This command may be used to list metadata of remote
files (e.g. URI beginning with "http://" or "smb://").
Clients that are connected via UNIX domain socket may
Clients that are connected via local socket may
use this command to read the tags of an arbitrary local
file (URI is an absolute path).
@@ -1046,7 +1081,8 @@ Stickers
"Stickers" [#since_0_15]_ are pieces of
information attached to existing
:program:`MPD` objects (e.g. song files,
directories, albums). Clients can create arbitrary name/value
directories, albums; but currently, they are only implemented for
song). Clients can create arbitrary name/value
pairs. :program:`MPD` itself does not assume
any special meaning in them.
@@ -1131,7 +1167,7 @@ Connection settings
``tagtypes`` sub commands configure this
list.
:command:`tagtypes disable {NAME...]`
:command:`tagtypes disable {NAME...}`
Remove one or more tags from the list of tag types the
client is interested in. These will be omitted from
responses to this client.
@@ -1215,7 +1251,7 @@ Reflection
:command:`config`
Dumps configuration values that may be interesting for
the client. This command is only permitted to "local"
clients (connected via UNIX domain socket).
clients (connected via local socket).
The following response attributes are available:

@@ -54,7 +54,7 @@ Download the source tarball from the `MPD home page <https://musicpd.org>`_ and
In any case, you need:
* a C++14 compiler (e.g. gcc 6.0 or clang 3.9)
* `Meson 0.47.2 <http://mesonbuild.com/>`__ and `Ninja
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* Boost 1.58
* pkg-config
@@ -365,10 +365,14 @@ More information can be found in the :ref:`decoder_plugins` reference.
Configuring encoder plugins
---------------------------
Encoders are used by some of the output plugins (such as shout). The encoder settings are included in the audio_output section.
Encoders are used by some of the output plugins (such as shout). The
encoder settings are included in the ``audio_output`` section, see :ref:`config_audio_output`.
More information can be found in the :ref:`encoder_plugins` reference.
.. _config_audio_output:
Configuring audio outputs
-------------------------
@@ -398,14 +402,9 @@ The following table lists the audio_output options valid for all plugins:
- The name of the plugin
* - **name**
- The name of the audio output. It is visible to the client. Some plugins also use it internally, e.g. as a name registered in the PULSE server.
* - **format**
- Always open the audio output with the specified audio format samplerate:bits:channels), regardless of the format of the input file. This is optional for most plugins.
Any of the three attributes may be an asterisk to specify that this attribute should not be enforced, example: 48000:16:*. *:*:* is equal to not having a format specification.
The following values are valid for bits: 8 (signed 8 bit integer samples), 16, 24 (signed 24 bit integer samples padded to 32 bit), 32 (signed 32 bit integer samples), f (32 bit floating point, -1.0 to 1.0), "dsd" means DSD (Direct Stream Digital). For DSD, there are special cases such as "dsd64", which allows you to omit the sample rate (e.g. dsd512:2 for stereo DSD512, i.e. 22.5792 MHz).
The sample rate is special for DSD: :program:`MPD` counts the number of bytes, not bits. Thus, a DSD "bit" rate of 22.5792 MHz (DSD512) is 2822400 from :program:`MPD`'s point of view (44100*512/8).
* - **format samplerate:bits:channels**
- Always open the audio output with the specified audio format, regardless of the format of the input file. This is optional for most plugins.
See :ref:`audio_output_format` for a detailed description of the value.
* - **enabed yes|no**
- Specifies whether this audio output is enabled when :program:`MPD` is started. By default, all audio outputs are enabled. This is just the default setting when there is no state file; with a state file, the previous state is restored.
* - **tags yes|no**
@@ -421,6 +420,15 @@ The following table lists the audio_output options valid for all plugins:
implement an external mixer :ref:`external_mixer`) or no mixer
(:samp:`none`). By default, the hardware mixer is used for
devices which support it, and none for the others.
* - **filters "name,...**"
- The specified configured filters are instantiated in the given
order. Each filter name refers to a ``filter`` block, see
:ref:`config_filter`.
More information can be found in the :ref:`output_plugins` reference.
.. _config_filter:
Configuring filters
-------------------
@@ -436,6 +444,9 @@ To configure a filter, add a :code:`filter` block to :file:`mpd.conf`:
name "software volume"
}
Configured filters may then be added to the ``filters`` setting of an
``audio_output`` section, see :ref:`config_audio_output`.
The following table lists the filter options valid for all plugins:
.. list-table::
@@ -449,6 +460,9 @@ The following table lists the filter options valid for all plugins:
* - **name**
- The name of the filter
More information can be found in the :ref:`filter_plugins` reference.
Configuring playlist plugins
----------------------------
@@ -485,13 +499,34 @@ reference.
Audio Format Settings
---------------------
Global Audio Format
~~~~~~~~~~~~~~~~~~~
.. _audio_output_format:
The setting audio_output_format forces :program:`MPD` to use one audio format for all outputs. Doing that is usually not a good idea. The values are the same as in format in the audio_output section.
Global Audio Format
^^^^^^^^^^^^^^^^^^^
The setting ``audio_output_format`` forces :program:`MPD` to use one
audio format for all outputs. Doing that is usually not a good idea.
The value is specified as ``samplerate:bits:channels``.
Any of the three attributes may be an asterisk to specify that this
attribute should not be enforced, example: ``48000:16:*``.
``*:*:*`` is equal to not having a format specification.
The following values are valid for bits: ``8`` (signed 8 bit integer
samples), ``16``, ``24`` (signed 24 bit integer samples padded to 32
bit), ``32`` (signed 32 bit integer samples), ``f`` (32 bit floating
point, -1.0 to 1.0), ``dsd`` means DSD (Direct Stream Digital). For
DSD, there are special cases such as ``dsd64``, which allows you to
omit the sample rate (e.g. ``dsd512:2`` for stereo DSD512,
i.e. 22.5792 MHz).
The sample rate is special for DSD: :program:`MPD` counts the number
of bytes, not bits. Thus, a DSD "bit" rate of 22.5792 MHz (DSD512) is
2822400 from :program:`MPD`'s point of view (44100*512/8).
Resampler
~~~~~~~~~
^^^^^^^^^
Sometimes, music needs to be resampled before it can be played; for example, CDs use a sample rate of 44,100 Hz while many cheap audio chips can only handle 48,000 Hz. Resampling reduces the quality and consumes a lot of CPU. There are different options, some of them optimized for high quality and others for low CPU usage, but you can't have both at the same time. Often, the resampler is the component that is responsible for most of :program:`MPD`'s CPU usage. Since :program:`MPD` comes with high quality defaults, it may appear that :program:`MPD` consumes more CPU than other software.
@@ -504,7 +539,7 @@ Client Connections
.. _listeners:
Listeners
~~~~~~~~~
^^^^^^^^^
The setting :code:`bind_to_address` specifies which addresses
:program:`MPD` listens on for connections from clients. It can be
@@ -531,6 +566,12 @@ choice::
bind_to_address "/var/run/mpd/socket"
On Linux, local sockets can be bound to a name without a socket inode
on the filesystem; MPD implements this by prepending ``@`` to the
address::
bind_to_address "@mpd"
If no port is specified, the default port is 6600. This default can
be changed with the port setting::
@@ -541,7 +582,7 @@ used.
Permissions and Passwords
~~~~~~~~~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^^^^^^^^^
By default, all clients are unauthenticated and have a full set of permissions. This can be restricted with the settings :code:`default_permissions` and :code:`password`.
@@ -604,7 +645,7 @@ Other Settings
Section :ref:`tags` contains a list of supported tags.
The State File
~~~~~~~~~~~~~~
^^^^^^^^^^^^^^
The state file is a file where :program:`MPD` saves and restores its state (play queue, playback position etc.) to keep it persistent across restarts and reboots. It is an optional setting.
@@ -622,7 +663,7 @@ The State File
- Auto-save the state file this number of seconds after each state change. Defaults to 120 (2 minutes).
The Sticker Database
~~~~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^^^^
"Stickers" are pieces of information attached to songs. Some clients
use them to store ratings and other volatile data. This feature
@@ -639,7 +680,7 @@ requires :program:`SQLite`, compile-time configure option
- The location of the sticker database.
Resource Limitations
~~~~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^^^^
These settings are various limitations to prevent :program:`MPD` from using too many resources (denial of service).
@@ -661,7 +702,7 @@ These settings are various limitations to prevent :program:`MPD` from using too
- The maximum size of the output buffer to a client (maximum response size). Default is 8192 (8 MiB).
Buffer Settings
~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^
Do not change these unless you know what you are doing.
@@ -675,7 +716,7 @@ Do not change these unless you know what you are doing.
- Adjust the size of the internal audio buffer. Default is 4096 (4 MiB).
Zeroconf
~~~~~~~~
^^^^^^^^
If Zeroconf support (`Avahi <http://avahi.org/>`_ or Apple's Bonjour)
was enabled at compile time with :code:`-Dzeroconf=...`,
@@ -761,10 +802,12 @@ You can verify whether the real-time scheduler is active with the ps command:
The CLS column shows the CPU scheduler; TS is the normal scheduler; FF and RR are real-time schedulers. In this example, two threads use the real-time scheduler: the output thread and the rtio (real-time I/O) thread; these two are the important ones. The database update thread uses the idle scheduler ("IDL in ps), which only gets CPU when no other process needs it.
Note
~~~~
.. note::
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.
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.
Using MPD
*********
@@ -792,7 +835,7 @@ Depending on the size of your music collection and the speed of the storage, thi
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
Mounting other storages into the music directory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:program:`MPD` has various storage plugins of which multiple instances can be "mounted" into the music directory. This way, you can use local music, file servers and USB sticks at the same time. Example:
@@ -860,7 +903,7 @@ To verify if :program:`MPD` converts the audio format, enable verbose logging, a
.. code-block:: none
decoder: audio_format=44100:24:2, seekable=true
output: opened plugin=alsa name="An ALSA output"audio_format=44100:16:2
output: opened plugin=alsa name="An ALSA output" audio_format=44100:16:2
output: converting from 44100:24:2
This example shows that a 24 bit file is being played, but the sound chip cannot play 24 bit. It falls back to 16 bit, discarding 8 bit.
@@ -887,7 +930,7 @@ Check list for bit-perfect playback:
device (:samp:`hw:0,0` or similar).
* Don't use software volume (setting :code:`mixer_type`).
* Don't force :program:`MPD` to use a specific audio format (settings
:code:`format`, :code:`audio_output_format`).
:code:`format`, :ref:`audio_output_format <audio_output_format>`).
* Verify that you are really doing bit-perfect playback using :program:`MPD`'s verbose log and :file:`/proc/asound/card*/pcm*p/sub*/hw_params`. Some DACs can also indicate the audio format.
Direct Stream Digital (DSD)
@@ -938,18 +981,18 @@ Support
-------
Getting Help
~~~~~~~~~~~~
^^^^^^^^^^^^
The :program:`MPD` project runs a `forum <https://forum.musicpd.org/>`_ and an IRC channel (#mpd on Freenode) for requesting help. Visit the MPD help page for details on how to get help.
Common Problems
~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^
1. Database
^^^^^^^^^^^
"""""""""""
Question: I can't see my music in the MPD database!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Check your :code:`music_directory` setting.
* Does the MPD user have read permission on all music files, and read+execute permission on all music directories (and all of their parent directories)?
@@ -957,22 +1000,22 @@ Question: I can't see my music in the MPD database!
* Did you enable all relevant decoder plugins at compile time? :command:`mpd --version` will tell you.
Question: MPD doesn't read ID3 tags!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* You probably compiled :program:`MPD` without libid3tag. :command:`mpd --version` will tell you.
2. Playback
^^^^^^^^^^^
"""""""""""
Question: I can't hear music on my client!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* That problem usually follows a misunderstanding of the nature of :program:`MPD`. :program:`MPD` is a remote-controlled music player, not a music distribution system. Usually, the speakers are connected to the box where :program:`MPD` runs, and the :program:`MPD` client only sends control commands, but the client does not actually play your music.
:program:`MPD` has output plugins which allow hearing music on a remote host (such as httpd), but that is not :program:`MPD`'s primary design goal.
Question: "Device or resource busy"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This ALSA error means that another program uses your sound hardware exclusively. You can stop that program to allow :program:`MPD` to use it.
@@ -991,7 +1034,7 @@ Your bug report should contain:
* be clear about what you expect MPD to do, and what is actually happening
MPD crashes
~~~~~~~~~~~
^^^^^^^^^^^
All :program:`MPD` crashes are bugs which must be fixed by a developer, and you should write a bug report. (Many crash bugs are caused by codec libraries used by :program:`MPD`, and then that library must be fixed; but in any case, the :program:`MPD` `bug tracker <https://github.com/MusicPlayerDaemon/MPD/issues>`_ is a good place to report it first if you don't know.)

@@ -1,8 +1,8 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.4',
meson_version: '>= 0.47.2',
version: '0.21.12',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',
'cpp_std=c++14'
@@ -15,12 +15,18 @@ version_cxx = vcs_tag(input: 'src/GitVersion.cxx', output: 'GitVersion.cxx')
compiler = meson.get_compiler('cpp')
c_compiler = meson.get_compiler('c')
if compiler.get_id() == 'gcc' and compiler.version().version_compare('<6')
warning('Your GCC version is too old. You need at least version 6.')
elif compiler.get_id() == 'clang' and compiler.version().version_compare('<3')
warning('Your clang version is too old. You need at least version 3.')
endif
conf = configuration_data()
conf.set_quoted('PACKAGE', meson.project_name())
conf.set_quoted('PACKAGE_NAME', meson.project_name())
conf.set_quoted('PACKAGE_VERSION', meson.project_version())
conf.set_quoted('VERSION', meson.project_version())
conf.set_quoted('PROTOCOL_VERSION', '0.21.4')
conf.set_quoted('PROTOCOL_VERSION', '0.21.11')
conf.set_quoted('SYSTEM_CONFIG_FILE_LOCATION', join_paths(get_option('prefix'), get_option('sysconfdir'), 'mpd.conf'))
common_cppflags = [
@@ -367,8 +373,10 @@ basic_dep = declare_dependency(
if enable_database
subdir('src/storage')
subdir('src/db')
else
storage_glue_dep = dependency('', required: false)
endif
subdir('src/db')
if neighbor_glue_dep.found()
sources += 'src/command/NeighborCommands.cxx'
@@ -390,6 +398,7 @@ more_deps = []
if is_android
subdir('src/java')
target_type = 'shared_library'
target_name = 'mpd'
link_args += [
'-Wl,--no-undefined,-shared,-Bsymbolic',
'-llog',
@@ -399,12 +408,20 @@ if is_android
declare_dependency(sources: [classes_jar]),
java_dep,
]
elif is_haiku
target_type = 'executable'
target_name = 'mpd.nores'
link_args += [
'-lnetwork',
'-lbe',
]
else
target_type = 'executable'
target_name = 'mpd'
endif
mpd = build_target(
'mpd',
target_name,
sources,
target_type: target_type,
include_directories: inc,
@@ -443,6 +460,14 @@ endif
if is_haiku
subdir('src/haiku')
custom_target(
'mpd',
output: 'mpd',
input: [mpd, rsrc],
command: [addres, '@OUTPUT@', '@INPUT0@', '@INPUT1@'],
install: true,
install_dir: get_option('bindir'),
)
endif
configure_file(output: 'config.h', configuration: conf)

@@ -128,6 +128,7 @@ option('mpg123', type: 'feature', description: 'MP3 decoder using libmpg123')
option('opus', type: 'feature', description: 'Opus decoder plugin')
option('sidplay', type: 'feature', description: 'C64 SID support via libsidplayfp or libsidplay2')
option('sndfile', type: 'feature', description: 'libsndfile decoder plugin')
option('tremor', type: 'feature', description: 'Fixed-point vorbis decoder plugin')
option('vorbis', type: 'feature', description: 'Vorbis decoder plugin')
option('wavpack', type: 'feature', description: 'WavPack decoder plugin')
option('wildmidi', type: 'feature', description: 'WildMidi decoder plugin')

@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.1.tar.xz',
'a38ec4d026efb58506a99ad5cd23d5a9793b4bf415f2c4c2e9c1bb444acd1994',
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.62.0.tar.xz',
'dab5643a5fe775ae92570b9f3df6b0ef4bc2a827a959361fb130c73b721275c1',
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -375,8 +375,8 @@ libexpat = AutotoolsProject(
)
libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-3.0.0.tar.gz',
'445d92c5fc55e4a5b115e358e60486cf8f87ee50e0103d46a02e7fb4618566a5',
'https://github.com/sahlberg/libnfs/archive/libnfs-4.0.0.tar.gz',
'6ee77e9fe220e2d3e3b1f53cfea04fb319828cc7dbb97dd9df09e46e901d797d',
'lib/libnfs.a',
[
'--disable-shared', '--enable-static',
@@ -387,12 +387,12 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples',
],
base='libnfs-libnfs-3.0.0',
base='libnfs-libnfs-4.0.0',
autoreconf=True,
)
boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.68.0/boost_1_68_0.tar.bz2',
'7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7',
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
'include/boost/version.hpp',
)

@@ -108,17 +108,17 @@ static constexpr Domain cmdline_domain("cmdline");
gcc_noreturn
static void version(void)
{
printf("Music Player Daemon " VERSION " (%s)\n"
printf("Music Player Daemon " VERSION " (%s)"
"\n"
"Copyright 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
"Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
GIT_VERSION);
#ifdef ENABLE_DATABASE
"\n"
"Database plugins:\n",
GIT_VERSION);
printf("\n"
"Database plugins:\n");
for (auto i = database_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
@@ -129,18 +129,18 @@ static void version(void)
for (auto i = storage_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
printf("\n"
printf("\n");
#endif
#ifdef ENABLE_NEIGHBOR_PLUGINS
"\n"
printf("\n"
"Neighbor plugins:\n");
for (auto i = neighbor_plugins; *i != nullptr; ++i)
printf(" %s", (*i)->name);
printf("\n"
#endif
printf("\n"
"\n"
"Decoders plugins:\n");

@@ -30,6 +30,9 @@
#ifdef ENABLE_DATABASE
#include "db/DatabaseError.hxx"
#include "db/Interface.hxx"
#include "db/update/Service.hxx"
#include "storage/StorageInterface.hxx"
#ifdef ENABLE_SQLITE
#include "sticker/StickerDatabase.hxx"
@@ -48,7 +51,19 @@ Instance::Instance()
{
}
Instance::~Instance() noexcept = default;
Instance::~Instance() noexcept
{
#ifdef ENABLE_DATABASE
delete update;
if (database != nullptr) {
database->Close();
database.reset();
}
delete storage;
#endif
}
Partition *
Instance::FindPartition(const char *name) noexcept

@@ -41,7 +41,7 @@ class NeighborGlue;
#ifdef ENABLE_DATABASE
#include "db/DatabaseListener.hxx"
class Database;
#include "db/Ptr.hxx"
class Storage;
class UpdateService;
#endif
@@ -104,7 +104,7 @@ struct Instance final
#endif
#ifdef ENABLE_DATABASE
Database *database;
DatabasePtr database;
/**
* This is really a #CompositeStorage. To avoid heavy include
@@ -147,7 +147,6 @@ struct Instance final
Partition *FindPartition(const char *name) noexcept;
void BeginShutdownPartitions() noexcept;
void FinishShutdownPartitions() noexcept;
#ifdef ENABLE_DATABASE
/**
@@ -156,7 +155,7 @@ struct Instance final
* music_directory was configured).
*/
Database *GetDatabase() {
return database;
return database.get();
}
/**
@@ -168,8 +167,6 @@ struct Instance final
#endif
void BeginShutdownUpdate() noexcept;
void FinishShutdownUpdate() noexcept;
void ShutdownDatabase() noexcept;
#ifdef ENABLE_CURL
void LookupRemoteTag(const char *uri) noexcept;

@@ -55,14 +55,26 @@ LocateFileUri(const char *uri, const Client *client
}
static LocatedUri
LocateAbsoluteUri(const char *uri
LocateAbsoluteUri(UriPluginKind kind, const char *uri
#ifdef ENABLE_DATABASE
, const Storage *storage
#endif
)
{
if (!uri_supported_scheme(uri))
throw std::runtime_error("Unsupported URI scheme");
switch (kind) {
case UriPluginKind::INPUT:
case UriPluginKind::STORAGE: // TODO: separate check for storage plugins
if (!uri_supported_scheme(uri))
throw std::runtime_error("Unsupported URI scheme");
break;
case UriPluginKind::PLAYLIST:
/* for now, no validation for playlist URIs; this is
more complicated because there are three ways to
identify which plugin to use: URI scheme, filename
suffix and MIME type */
break;
}
#ifdef ENABLE_DATABASE
if (storage != nullptr) {
@@ -76,7 +88,8 @@ LocateAbsoluteUri(const char *uri
}
LocatedUri
LocateUri(const char *uri, const Client *client
LocateUri(UriPluginKind kind,
const char *uri, const Client *client
#ifdef ENABLE_DATABASE
, const Storage *storage
#endif
@@ -100,7 +113,7 @@ LocateUri(const char *uri, const Client *client
#endif
);
else if (uri_has_scheme(uri))
return LocateAbsoluteUri(uri
return LocateAbsoluteUri(kind, uri
#ifdef ENABLE_DATABASE
, storage
#endif

@@ -41,6 +41,12 @@ class Client;
class Storage;
#endif
enum class UriPluginKind {
INPUT,
STORAGE,
PLAYLIST,
};
struct LocatedUri {
enum class Type {
/**
@@ -84,7 +90,8 @@ struct LocatedUri {
* that feature is disabled if this parameter is nullptr
*/
LocatedUri
LocateUri(const char *uri, const Client *client
LocateUri(UriPluginKind kind,
const char *uri, const Client *client
#ifdef ENABLE_DATABASE
, const Storage *storage
#endif

@@ -109,7 +109,7 @@ parse_log_level(const char *value, int line)
#endif
void
log_early_init(bool verbose)
log_early_init(bool verbose) noexcept
{
#ifdef ANDROID
(void)verbose;
@@ -171,7 +171,7 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
#ifndef ANDROID
static void
close_log_files(void)
close_log_files() noexcept
{
#ifdef HAVE_SYSLOG
LogFinishSysLog();
@@ -181,7 +181,7 @@ close_log_files(void)
#endif
void
log_deinit(void)
log_deinit() noexcept
{
#ifndef ANDROID
close_log_files();
@@ -213,7 +213,8 @@ void setup_log_output()
#endif
}
int cycle_log_files(void)
int
cycle_log_files() noexcept
{
#ifdef ANDROID
return 0;

@@ -31,7 +31,7 @@ struct ConfigData;
* @param verbose true when the program is started with --verbose
*/
void
log_early_init(bool verbose);
log_early_init(bool verbose) noexcept;
/**
* Throws #std::runtime_error on error.
@@ -40,12 +40,12 @@ void
log_init(const ConfigData &config, bool verbose, bool use_stdout);
void
log_deinit();
log_deinit() noexcept;
void
setup_log_output();
int
cycle_log_files();
cycle_log_files() noexcept;
#endif /* LOG_H */

@@ -185,19 +185,16 @@ InitStorage(const ConfigData &config, EventLoop &event_loop)
static bool
glue_db_init_and_load(const ConfigData &config)
{
instance->database =
CreateConfiguredDatabase(config, instance->event_loop,
instance->io_thread.GetEventLoop(),
*instance);
if (instance->database == nullptr)
auto db = CreateConfiguredDatabase(config, instance->event_loop,
instance->io_thread.GetEventLoop(),
*instance);
if (!db)
return true;
if (instance->database->GetPlugin().RequireStorage()) {
if (db->GetPlugin().RequireStorage()) {
InitStorage(config, instance->io_thread.GetEventLoop());
if (instance->storage == nullptr) {
delete instance->database;
instance->database = nullptr;
LogDefault(config_domain,
"Found database setting without "
"music_directory - disabling database");
@@ -211,22 +208,24 @@ glue_db_init_and_load(const ConfigData &config)
}
try {
instance->database->Open();
db->Open();
} catch (...) {
std::throw_with_nested(std::runtime_error("Failed to open database plugin"));
}
auto *db = dynamic_cast<SimpleDatabase *>(instance->database);
if (db == nullptr)
instance->database = std::move(db);
auto *sdb = dynamic_cast<SimpleDatabase *>(instance->database.get());
if (sdb == nullptr)
return true;
instance->update = new UpdateService(config,
instance->event_loop, *db,
instance->event_loop, *sdb,
static_cast<CompositeStorage &>(*instance->storage),
*instance);
/* run database update after daemonization? */
return db->FileExists();
return sdb->FileExists();
}
static bool
@@ -351,27 +350,6 @@ Instance::BeginShutdownUpdate() noexcept
#endif
}
inline void
Instance::FinishShutdownUpdate() noexcept
{
#ifdef ENABLE_DATABASE
delete update;
#endif
}
inline void
Instance::ShutdownDatabase() noexcept
{
#ifdef ENABLE_DATABASE
if (instance->database != nullptr) {
instance->database->Close();
delete instance->database;
}
delete instance->storage;
#endif
}
inline void
Instance::BeginShutdownPartitions() noexcept
{
@@ -381,12 +359,6 @@ Instance::BeginShutdownPartitions() noexcept
}
}
inline void
Instance::FinishShutdownPartitions() noexcept
{
partitions.clear();
}
void
Instance::OnIdle(unsigned flags)
{
@@ -523,18 +495,19 @@ static int
mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
{
ConfigureFS(raw_config);
AtScopeExit() { DeinitFS(); };
glue_mapper_init(raw_config);
initPermissions(raw_config);
spl_global_init(raw_config);
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
const ScopeArchivePluginsInit archive_plugins_init;
#endif
pcm_convert_global_init(raw_config);
decoder_plugin_init_all(raw_config);
const ScopeDecoderPluginsInit decoder_plugins_init(raw_config);
#ifdef ENABLE_DATABASE
const bool create_db = InitDatabaseAndStorage(raw_config);
@@ -553,9 +526,9 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
}
client_manager_init(raw_config);
input_stream_global_init(raw_config,
instance->io_thread.GetEventLoop());
playlist_list_global_init(raw_config);
const ScopeInputPluginsInit input_plugins_init(raw_config,
instance->io_thread.GetEventLoop());
const ScopePlaylistPluginsInit playlist_plugins_init(raw_config);
#ifdef ENABLE_DAEMON
daemonize_commit();
@@ -564,7 +537,7 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
#ifndef ANDROID
setup_log_output();
SignalHandlersInit(instance->event_loop);
const ScopeSignalHandlersInit signal_handlers_init(instance->event_loop);
#endif
instance->io_thread.Start();
@@ -652,34 +625,10 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
}
#endif
instance->FinishShutdownUpdate();
instance->ShutdownDatabase();
#ifdef ENABLE_SQLITE
sticker_global_finish();
#endif
playlist_list_global_finish();
input_stream_global_finish();
#ifdef ENABLE_DATABASE
mapper_finish();
#endif
DeinitFS();
instance->FinishShutdownPartitions();
command_finish();
decoder_plugin_deinit_all();
#ifdef ENABLE_ARCHIVE
archive_plugin_deinit_all();
#endif
instance->rtio_thread.Stop();
instance->io_thread.Stop();
#ifndef ANDROID
SignalHandlersFinish();
#endif
return EXIT_SUCCESS;
}

@@ -58,11 +58,6 @@ mapper_init(AllocatedPath &&_playlist_dir)
mapper_set_playlist_dir(std::move(_playlist_dir));
}
void
mapper_finish() noexcept
{
}
#ifdef ENABLE_DATABASE
AllocatedPath

@@ -37,9 +37,6 @@ class AllocatedPath;
void
mapper_init(AllocatedPath &&playlist_dir);
void
mapper_finish() noexcept;
#ifdef ENABLE_DATABASE
/**

@@ -43,7 +43,15 @@ struct MusicChunk;
/**
* Meta information for #MusicChunk.
*/
struct MusicChunkInfo {
struct alignas(8) MusicChunkInfo {
/* align to multiple of 8 bytes, which adds padding at the
end, so the size of MusicChunk::data is also a multiple of
8 bytes; this is a workaround for a bug in the DSD_U32 and
DoP converters which require processing 8 bytes at a time,
discarding the remainder */
/* TODO: once all converters have been fixed, we should remove
this workaround */
/** the next chunk in a linked list */
MusicChunkPtr next;
@@ -119,6 +127,10 @@ struct MusicChunk : MusicChunkInfo {
/** the data (probably PCM) */
uint8_t data[CHUNK_SIZE - sizeof(MusicChunkInfo)];
/* TODO: remove this check once all converters have been fixed
(see comment in struct MusicChunkInfo for details) */
static_assert(sizeof(data) % 8 == 0, "Wrong alignment");
/**
* Prepares appending to the music chunk. Returns a buffer
* where you may write into. After you are finished, call

@@ -134,7 +134,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info,
const auto *const name_fs_end =
FindStringSuffix(name_fs_str,
PATH_LITERAL(PLAYLIST_FILE_SUFFIX));
if (name_fs_end == nullptr)
if (name_fs_end == nullptr ||
/* no empty playlist names (raw file name = ".m3u") */
name_fs_end == name_fs_str)
return false;
FileInfo fi;

@@ -94,7 +94,8 @@ SongLoader::LoadSong(const char *uri_utf8) const
assert(uri_utf8 != nullptr);
#endif
const auto located_uri = LocateUri(uri_utf8, client
const auto located_uri = LocateUri(UriPluginKind::INPUT,
uri_utf8, client
#ifdef ENABLE_DATABASE
, storage
#endif

@@ -119,7 +119,7 @@ try {
TextFile file(config.path);
#ifdef ENABLE_DATABASE
const SongLoader song_loader(partition.instance.database,
const SongLoader song_loader(partition.instance.GetDatabase(),
partition.instance.storage);
#else
const SongLoader song_loader(nullptr, nullptr);

@@ -124,7 +124,7 @@ stats_print(Response &r, const Partition &partition)
std::lround(partition.pc.GetTotalPlayTime().count()));
#ifdef ENABLE_DATABASE
const Database *db = partition.instance.database;
const Database *db = partition.instance.GetDatabase();
if (db != nullptr)
db_stats_print(r, *db);
#endif

@@ -25,8 +25,9 @@
void
tag_print_types(Response &r) noexcept
{
const auto tag_mask = global_tag_mask & r.GetTagMask();
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
if (IsTagEnabled(i))
if (tag_mask.Test(TagType(i)))
r.Format("tagtype: %s\n", tag_item_names[i]);
}

@@ -49,7 +49,7 @@ static bool archive_plugins_enabled[ARRAY_SIZE(archive_plugins) - 1];
if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins])
const ArchivePlugin *
archive_plugin_from_suffix(const char *suffix)
archive_plugin_from_suffix(const char *suffix) noexcept
{
if (suffix == nullptr)
return nullptr;
@@ -63,7 +63,7 @@ archive_plugin_from_suffix(const char *suffix)
}
const ArchivePlugin *
archive_plugin_from_name(const char *name)
archive_plugin_from_name(const char *name) noexcept
{
archive_plugins_for_each_enabled(plugin)
if (strcmp(plugin->name, name) == 0)
@@ -81,7 +81,8 @@ void archive_plugin_init_all(void)
}
}
void archive_plugin_deinit_all(void)
void
archive_plugin_deinit_all() noexcept
{
archive_plugins_for_each_enabled(plugin)
if (plugin->finish != nullptr)

@@ -33,10 +33,10 @@ extern const ArchivePlugin *const archive_plugins[];
/* interface for using plugins */
const ArchivePlugin *
archive_plugin_from_suffix(const char *suffix);
archive_plugin_from_suffix(const char *suffix) noexcept;
const ArchivePlugin *
archive_plugin_from_name(const char *name);
archive_plugin_from_name(const char *name) noexcept;
/* this is where we "load" all the "plugins" ;-) */
void
@@ -44,6 +44,17 @@ archive_plugin_init_all();
/* this is where we "unload" all the "plugins" */
void
archive_plugin_deinit_all();
archive_plugin_deinit_all() noexcept;
class ScopeArchivePluginsInit {
public:
ScopeArchivePluginsInit() {
archive_plugin_init_all();
}
~ScopeArchivePluginsInit() noexcept {
archive_plugin_deinit_all();
}
};
#endif

@@ -285,11 +285,6 @@ command_init()
#endif
}
void
command_finish()
{
}
static const struct command *
command_lookup(const char *name)
{

@@ -266,9 +266,12 @@ handle_list(Client &client, Request args, Response &r)
}
std::unique_ptr<SongFilter> filter;
TagType group = TAG_NUM_OF_ITEM_TYPES;
std::vector<TagType> tag_types;
if (args.size == 1) {
if (args.size == 1 &&
/* parantheses are the syntax for filter expressions: no
compatibility mode */
args.front()[0] != '(') {
/* for compatibility with < 0.12.0 */
if (tagType != TAG_ALBUM) {
r.FormatError(ACK_ERROR_ARG,
@@ -281,20 +284,31 @@ handle_list(Client &client, Request args, Response &r)
args.shift()));
}
if (args.size >= 2 &&
StringIsEqual(args[args.size - 2], "group")) {
while (args.size >= 2 &&
StringIsEqual(args[args.size - 2], "group")) {
const char *s = args[args.size - 1];
group = tag_name_parse_i(s);
const auto group = tag_name_parse_i(s);
if (group == TAG_NUM_OF_ITEM_TYPES) {
r.FormatError(ACK_ERROR_ARG,
"Unknown tag type: %s", s);
return CommandResult::ERROR;
}
if (group == tagType ||
std::find(tag_types.begin(), tag_types.end(),
group) != tag_types.end()) {
r.Error(ACK_ERROR_ARG, "Conflicting group");
return CommandResult::ERROR;
}
tag_types.emplace_back(group);
args.pop_back();
args.pop_back();
}
tag_types.emplace_back(tagType);
if (!args.empty()) {
filter.reset(new SongFilter());
try {
@@ -307,13 +321,9 @@ handle_list(Client &client, Request args, Response &r)
filter->Optimize();
}
if (tagType < TAG_NUM_OF_ITEM_TYPES && tagType == group) {
r.Error(ACK_ERROR_ARG, "Conflicting group");
return CommandResult::ERROR;
}
PrintUniqueTags(r, client.GetPartition(),
tagType, group, filter.get());
{&tag_types.front(), tag_types.size()},
filter.get());
return CommandResult::OK;
}

@@ -37,9 +37,11 @@
#include "fs/FileInfo.hxx"
#include "fs/DirectoryReader.hxx"
#include "input/InputStream.hxx"
#include "input/Error.hxx"
#include "LocateUri.hxx"
#include "TimePrint.hxx"
#include "thread/Mutex.hxx"
#include "Log.hxx"
#include <assert.h>
#include <inttypes.h> /* for PRIu64 */
@@ -216,7 +218,7 @@ handle_read_comments(Client &client, Request args, Response &r)
const char *const uri = args.front();
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
@@ -256,7 +258,11 @@ find_stream_art(const char *directory, Mutex &mutex)
try {
return InputStream::OpenReady(art_file.c_str(), mutex);
} catch (const std::exception &e) {}
} catch (...) {
auto e = std::current_exception();
if (!IsFileNotFound(e))
LogError(e);
}
}
return nullptr;
}
@@ -285,8 +291,11 @@ read_stream_art(Response &r, const char *uri, size_t offset)
uint8_t buffer[CHUNK_SIZE];
size_t read_size;
is->Seek(offset);
read_size = is->Read(&buffer, CHUNK_SIZE);
{
const std::lock_guard<Mutex> protect(mutex);
is->Seek(offset);
read_size = is->Read(&buffer, CHUNK_SIZE);
}
r.Format("size: %" PRIoffset "\n"
"binary: %u\n",
@@ -322,7 +331,7 @@ handle_album_art(Client &client, Request args, Response &r)
const char *uri = args.front();
size_t offset = args.ParseUnsigned(1);
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif

@@ -99,7 +99,7 @@ handle_listfiles(Client &client, Request args, Response &r)
/* default is root directory */
const auto uri = args.GetOptional(0, "");
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::STORAGE, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
@@ -219,7 +219,7 @@ handle_lsinfo(Client &client, Request args, Response &r)
compatibility, work around this here */
uri = "";
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
#ifdef ENABLE_DATABASE
, nullptr
#endif
@@ -294,7 +294,7 @@ handle_update(Client &client, Request args, Response &r, bool discard)
if (update != nullptr)
return handle_update(r, *update, path, discard);
Database *db = client.GetInstance().database;
Database *db = client.GetInstance().GetDatabase();
if (db != nullptr)
return handle_update(r, *db, path, discard);
#else

@@ -20,6 +20,7 @@
#include "config.h"
#include "PlaylistCommands.hxx"
#include "Request.hxx"
#include "Instance.hxx"
#include "db/DatabasePlaylist.hxx"
#include "CommandError.hxx"
#include "PlaylistSave.hxx"
@@ -27,6 +28,7 @@
#include "PlaylistError.hxx"
#include "db/PlaylistVector.hxx"
#include "SongLoader.hxx"
#include "song/DetachedSong.hxx"
#include "BulkEdit.hxx"
#include "playlist/PlaylistQueue.hxx"
#include "playlist/Print.hxx"
@@ -38,6 +40,7 @@
#include "util/UriUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/ChronoUtil.hxx"
#include "LocateUri.hxx"
bool
playlist_commands_available() noexcept
@@ -66,22 +69,43 @@ handle_save(Client &client, Request args, gcc_unused Response &r)
CommandResult
handle_load(Client &client, Request args, gcc_unused Response &r)
{
const auto uri = LocateUri(UriPluginKind::PLAYLIST, args.front(),
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
RangeArg range = args.ParseOptional(1, RangeArg::All());
const ScopeBulkEdit bulk_edit(client.GetPartition());
auto &playlist = client.GetPlaylist();
const unsigned old_size = playlist.GetLength();
const SongLoader loader(client);
playlist_open_into_queue(args.front(),
playlist_open_into_queue(uri,
range.start, range.end,
client.GetPlaylist(),
playlist,
client.GetPlayerControl(), loader);
/* invoke the RemoteTagScanner on all newly added songs */
auto &instance = client.GetInstance();
const unsigned new_size = playlist.GetLength();
for (unsigned i = old_size; i < new_size; ++i)
instance.LookupRemoteTag(playlist.queue.Get(i).GetURI());
return CommandResult::OK;
}
CommandResult
handle_listplaylist(Client &client, Request args, Response &r)
{
const char *const name = args.front();
const auto name = LocateUri(UriPluginKind::PLAYLIST, args.front(),
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
name, false))
@@ -93,7 +117,12 @@ handle_listplaylist(Client &client, Request args, Response &r)
CommandResult
handle_listplaylistinfo(Client &client, Request args, Response &r)
{
const char *const name = args.front();
const auto name = LocateUri(UriPluginKind::PLAYLIST, args.front(),
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif
);
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
name, true))

@@ -83,7 +83,8 @@ handle_add(Client &client, Request args, Response &r)
here */
uri = "";
const auto located_uri = LocateUri(uri, &client
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
&client
#ifdef ENABLE_DATABASE
, nullptr
#endif

@@ -209,7 +209,7 @@ handle_mount(Client &client, Request args, Response &r)
instance.EmitIdle(IDLE_MOUNT);
#ifdef ENABLE_DATABASE
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.database)) {
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
try {
db->Mount(local_uri, remote_uri);
} catch (...) {
@@ -253,7 +253,7 @@ handle_unmount(Client &client, Request args, Response &r)
destroy here */
instance.update->CancelMount(local_uri);
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.database)) {
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
if (db->Unmount(local_uri))
// TODO: call Instance::OnDatabaseModified()?
instance.EmitIdle(IDLE_DATABASE);

@@ -29,6 +29,8 @@ ServerSocketAddGeneric(ServerSocket &server_socket, const char *address, unsigne
server_socket.AddPort(port);
} else if (address[0] == '/' || address[0] == '~') {
server_socket.AddPath(ParsePath(address));
} else if (address[0] == '@') {
server_socket.AddAbstract(address);
} else {
server_socket.AddHost(address, port);
}

@@ -23,14 +23,14 @@
class ServerSocket;
/**
* Sets the address or unix socket of a ServerSocket instance
* Sets the address or local socket of a ServerSocket instance
* There are three possible ways
* 1) Set address to a valid ip address and specify port.
* server_socket will listen on this address/port tuple.
* 2) Set address to null and specify port.
* server_socket will listen on ANY address on that port.
* 3) Set address to a path of a unix socket. port is ignored.
* server_socket will listen on this unix socket.
* 3) Set address to a path of a local socket. port is ignored.
* server_socket will listen on this local socket.
*
* Throws #std::runtime_error on error.
*

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "Configured.hxx"
#include "DatabaseGlue.hxx"
#include "Interface.hxx"
#include "config/Data.hxx"
#include "config/Param.hxx"
#include "config/Block.hxx"
@@ -26,7 +27,7 @@
#include "fs/StandardDirectory.hxx"
#include "util/RuntimeError.hxx"
Database *
DatabasePtr
CreateConfiguredDatabase(const ConfigData &config,
EventLoop &main_event_loop, EventLoop &io_event_loop,
DatabaseListener &listener)

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,10 +20,11 @@
#ifndef MPD_DB_CONFIG_HXX
#define MPD_DB_CONFIG_HXX
#include "Ptr.hxx"
struct ConfigData;
class EventLoop;
class DatabaseListener;
class Database;
/**
* Read database configuration settings and create a #Database
@@ -32,7 +33,7 @@ class Database;
*
* Throws #std::runtime_error on error.
*/
Database *
DatabasePtr
CreateConfiguredDatabase(const ConfigData &config,
EventLoop &main_event_loop, EventLoop &io_event_loop,
DatabaseListener &listener);

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,13 +18,14 @@
*/
#include "DatabaseGlue.hxx"
#include "Interface.hxx"
#include "Registry.hxx"
#include "DatabaseError.hxx"
#include "util/RuntimeError.hxx"
#include "config/Block.hxx"
#include "DatabasePlugin.hxx"
Database *
DatabasePtr
DatabaseGlobalInit(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,12 +20,11 @@
#ifndef MPD_DATABASE_GLUE_HXX
#define MPD_DATABASE_GLUE_HXX
#include "util/Compiler.h"
#include "Ptr.hxx"
struct ConfigBlock;
class EventLoop;
class DatabaseListener;
class Database;
/**
* Initialize the database library.
@@ -34,7 +33,7 @@ class Database;
*
* @param block the database configuration block
*/
Database *
DatabasePtr
DatabaseGlobalInit(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,10 +26,11 @@
#ifndef MPD_DATABASE_PLUGIN_HXX
#define MPD_DATABASE_PLUGIN_HXX
#include "Ptr.hxx"
struct ConfigBlock;
class EventLoop;
class DatabaseListener;
class Database;
struct DatabasePlugin {
/**
@@ -52,10 +53,10 @@ struct DatabasePlugin {
* @param io_event_loop the #EventLoop running on the
* #EventThread, i.e. the one used for background I/O
*/
Database *(*create)(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
DatabasePtr (*create)(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
constexpr bool RequireStorage() const {
return flags & FLAG_REQUIRE_STORAGE;

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -35,6 +35,7 @@
#include "Interface.hxx"
#include "fs/Traits.hxx"
#include "util/ChronoUtil.hxx"
#include "util/RecursiveMap.hxx"
#include <functional>
@@ -186,42 +187,29 @@ PrintSongUris(Response &r, Partition &partition,
}
static void
PrintUniqueTags(Response &r, TagType tag_type,
const std::set<std::string> &values)
PrintUniqueTags(Response &r, ConstBuffer<TagType> tag_types,
const RecursiveMap<std::string> &map) noexcept
{
const char *const name = tag_item_names[tag_type];
for (const auto &i : values)
r.Format("%s: %s\n", name, i.c_str());
}
const char *const name = tag_item_names[tag_types.front()];
tag_types.pop_front();
static void
PrintGroupedUniqueTags(Response &r, TagType tag_type, TagType group,
const std::map<std::string, std::set<std::string>> &groups)
{
if (group == TAG_NUM_OF_ITEM_TYPES) {
for (const auto &i : groups)
PrintUniqueTags(r, tag_type, i.second);
return;
}
for (const auto &i : map) {
r.Format("%s: %s\n", name, i.first.c_str());
const char *const group_name = tag_item_names[group];
for (const auto &i : groups) {
r.Format("%s: %s\n", group_name, i.first.c_str());
PrintUniqueTags(r, tag_type, i.second);
if (!tag_types.empty())
PrintUniqueTags(r, tag_types, i.second);
}
}
void
PrintUniqueTags(Response &r, Partition &partition,
TagType type, TagType group,
ConstBuffer<TagType> tag_types,
const SongFilter *filter)
{
assert(type < TAG_NUM_OF_ITEM_TYPES);
const Database &db = partition.GetDatabaseOrThrow();
const DatabaseSelection selection("", true, filter);
PrintGroupedUniqueTags(r, type, group,
db.CollectUniqueTags(selection, type, group));
PrintUniqueTags(r, tag_types,
db.CollectUniqueTags(selection, tag_types));
}

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
#include <stdint.h>
template<typename T> struct ConstBuffer;
enum TagType : uint8_t;
class TagMask;
class SongFilter;
@@ -45,7 +46,7 @@ PrintSongUris(Response &r, Partition &partition,
void
PrintUniqueTags(Response &r, Partition &partition,
TagType type, TagType group,
ConstBuffer<TagType> tag_types,
const SongFilter *filter);
#endif

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,15 +25,14 @@
#include "util/Compiler.h"
#include <chrono>
#include <map>
#include <set>
#include <string>
struct DatabasePlugin;
struct DatabaseStats;
struct DatabaseSelection;
struct LightSong;
class TagMask;
template<typename Key> class RecursiveMap;
template<typename T> struct ConstBuffer;
class Database {
const DatabasePlugin &plugin;
@@ -106,13 +105,14 @@ public:
}
/**
* Collect unique values of the given tag type.
* Collect unique values of the given tag types. Each item in
* the #tag_types parameter results in one nesting level in
* the return value.
*
* Throws on error.
*/
virtual std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
TagType tag_type,
TagType group=TAG_NUM_OF_ITEM_TYPES) const = 0;
virtual RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
ConstBuffer<TagType> tag_types) const = 0;
/**
* Throws on error.

29
src/db/Ptr.hxx Normal file

@@ -0,0 +1,29 @@
/*
* Copyright 2003-2019 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_DATABASE_PTR_HXX
#define MPD_DATABASE_PTR_HXX
#include <memory>
class Database;
typedef std::unique_ptr<Database> DatabasePtr;
#endif

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,36 +21,32 @@
#include "Interface.hxx"
#include "song/LightSong.hxx"
#include "tag/VisitFallback.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RecursiveMap.hxx"
static void
CollectTags(std::set<std::string> &result,
const Tag &tag,
TagType tag_type) noexcept
CollectUniqueTags(RecursiveMap<std::string> &result,
const Tag &tag,
ConstBuffer<TagType> tag_types) noexcept
{
VisitTagWithFallbackOrEmpty(tag, tag_type, [&result](const char *value){
result.emplace(value);
if (tag_types.empty())
return;
const auto tag_type = tag_types.shift();
VisitTagWithFallbackOrEmpty(tag, tag_type, [&result, &tag, tag_types](const char *value){
CollectUniqueTags(result[value], tag, tag_types);
});
}
static void
CollectGroupTags(std::map<std::string, std::set<std::string>> &result,
const Tag &tag,
TagType tag_type,
TagType group) noexcept
{
VisitTagWithFallbackOrEmpty(tag, group, [&](const char *group_name){
CollectTags(result[group_name], tag, tag_type);
});
}
std::map<std::string, std::set<std::string>>
RecursiveMap<std::string>
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
TagType tag_type, TagType group)
ConstBuffer<TagType> tag_types)
{
std::map<std::string, std::set<std::string>> result;
RecursiveMap<std::string> result;
db.Visit(selection, [&result, tag_type, group](const LightSong &song){
CollectGroupTags(result, song.tag, tag_type, group);
db.Visit(selection, [&result, tag_types](const LightSong &song){
CollectUniqueTags(result, song.tag, tag_types);
});
return result;

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,9 +29,11 @@
class TagMask;
class Database;
struct DatabaseSelection;
template<typename Key> class RecursiveMap;
template<typename T> struct ConstBuffer;
std::map<std::string, std::set<std::string>>
RecursiveMap<std::string>
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
TagType tag_type, TagType group);
ConstBuffer<TagType> tag_types);
#endif

@@ -18,11 +18,11 @@
*/
#include "VHelper.hxx"
#include "song/DetachedSong.hxx"
#include "song/LightSong.hxx"
#include "song/Filter.hxx"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
DatabaseVisitorHelper::DatabaseVisitorHelper(const DatabaseSelection &_selection,

@@ -22,6 +22,7 @@
#include "Visitor.hxx"
#include "Selection.hxx"
#include "song/DetachedSong.hxx"
#include <vector>

@@ -9,6 +9,11 @@ db_api_dep = declare_dependency(
link_with: db_api,
)
if not enable_database
db_glue_dep = db_api_dep
subdir_done()
endif
subdir('plugins')
db_glue_sources = [

@@ -38,6 +38,8 @@
#include "tag/Tag.hxx"
#include "tag/Mask.hxx"
#include "tag/ParseName.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RecursiveMap.hxx"
#include "util/ScopeExit.hxx"
#include "util/RuntimeError.hxx"
#include "protocol/Ack.hxx"
@@ -112,10 +114,10 @@ public:
ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
const ConfigBlock &block);
static Database *Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
static DatabasePtr Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
void Open() override;
void Close() noexcept override;
@@ -127,9 +129,8 @@ public:
VisitSong visit_song,
VisitPlaylist visit_playlist) const override;
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
TagType tag_type,
TagType group) const override;
RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
ConstBuffer<TagType> tag_types) const override;
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
@@ -412,8 +413,7 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
static bool
SendGroup(mpd_connection *connection, TagType group)
{
if (group == TAG_NUM_OF_ITEM_TYPES)
return true;
assert(group != TAG_NUM_OF_ITEM_TYPES);
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
const auto tag = Convert(group);
@@ -428,6 +428,19 @@ SendGroup(mpd_connection *connection, TagType group)
#endif
}
static bool
SendGroup(mpd_connection *connection, ConstBuffer<TagType> group)
{
while (!group.empty()) {
if (!SendGroup(connection, group.back()))
return false;
group.pop_back();
}
return true;
}
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
const ConfigBlock &block)
:Database(proxy_db_plugin),
@@ -440,12 +453,12 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
{
}
Database *
DatabasePtr
ProxyDatabase::Create(EventLoop &loop, EventLoop &,
DatabaseListener &listener,
const ConfigBlock &block)
{
return new ProxyDatabase(loop, listener, block);
return std::make_unique<ProxyDatabase>(loop, listener, block);
}
void
@@ -568,7 +581,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
if (!is_idle) {
// TODO: can this happen?
IdleMonitor::Schedule();
return false;
SocketMonitor::Cancel();
return true;
}
unsigned idle = (unsigned)mpd_recv_idle(connection, false);
@@ -586,7 +600,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
idle_received |= idle;
is_idle = false;
IdleMonitor::Schedule();
return false;
SocketMonitor::Cancel();
return true;
}
void
@@ -981,17 +996,20 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
helper.Commit();
}
std::map<std::string, std::set<std::string>>
RecursiveMap<std::string>
ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType group) const
ConstBuffer<TagType> tag_types) const
try {
// TODO: eliminate the const_cast
const_cast<ProxyDatabase *>(this)->EnsureConnected();
enum mpd_tag_type tag_type2 = Convert(tag_type);
enum mpd_tag_type tag_type2 = Convert(tag_types.back());
if (tag_type2 == MPD_TAG_COUNT)
throw std::runtime_error("Unsupported tag");
auto group = tag_types;
group.pop_back();
if (!mpd_search_db_tags(connection, tag_type2) ||
!SendConstraints(connection, selection) ||
!SendGroup(connection, group))
@@ -1000,44 +1018,33 @@ try {
if (!mpd_search_commit(connection))
ThrowError(connection);
std::map<std::string, std::set<std::string>> result;
RecursiveMap<std::string> result;
std::vector<RecursiveMap<std::string> *> position;
position.emplace_back(&result);
if (group == TAG_NUM_OF_ITEM_TYPES) {
auto &values = result[std::string()];
while (auto *pair = mpd_recv_pair(connection)) {
AtScopeExit(this, pair) {
mpd_return_pair(connection, pair);
};
while (auto *pair = mpd_recv_pair(connection)) {
AtScopeExit(this, pair) {
mpd_return_pair(connection, pair);
};
const auto current_type = tag_name_parse_i(pair->name);
if (current_type == TAG_NUM_OF_ITEM_TYPES)
continue;
const auto current_type = tag_name_parse_i(pair->name);
if (current_type == TAG_NUM_OF_ITEM_TYPES)
continue;
auto it = std::find(tag_types.begin(), tag_types.end(),
current_type);
if (it == tag_types.end())
continue;
if (current_type == tag_type)
values.emplace(pair->value);
}
} else {
std::set<std::string> *current_group = nullptr;
size_t i = std::distance(tag_types.begin(), it);
if (i > position.size())
continue;
while (auto *pair = mpd_recv_pair(connection)) {
AtScopeExit(this, pair) {
mpd_return_pair(connection, pair);
};
if (i + 1 < position.size())
position.resize(i + 1);
const auto current_type = tag_name_parse_i(pair->name);
if (current_type == TAG_NUM_OF_ITEM_TYPES)
continue;
if (current_type == tag_type) {
if (current_group == nullptr)
current_group = &result[std::string()];
current_group->emplace(pair->value);
} else if (current_type == group) {
current_group = &result[pair->value];
}
}
auto &parent = *position[i];
position.emplace_back(&parent[pair->value]);
}
if (!mpd_response_finish(connection))

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -37,22 +37,25 @@
#include <string.h>
#include <stdlib.h>
Directory::Directory(std::string &&_path_utf8, Directory *_parent)
Directory::Directory(std::string &&_path_utf8, Directory *_parent) noexcept
:parent(_parent),
path(std::move(_path_utf8))
{
}
Directory::~Directory()
Directory::~Directory() noexcept
{
delete mounted_database;
if (mounted_database != nullptr) {
mounted_database->Close();
mounted_database.reset();
}
songs.clear_and_dispose(Song::Disposer());
children.clear_and_dispose(DeleteDisposer());
}
void
Directory::Delete()
Directory::Delete() noexcept
{
assert(holding_db_lock());
assert(parent != nullptr);
@@ -70,7 +73,7 @@ Directory::GetName() const noexcept
}
Directory *
Directory::CreateChild(const char *name_utf8)
Directory::CreateChild(const char *name_utf8) noexcept
{
assert(holding_db_lock());
assert(name_utf8 != nullptr);
@@ -160,7 +163,7 @@ Directory::LookupDirectory(const char *uri) noexcept
}
void
Directory::AddSong(Song *song)
Directory::AddSong(Song *song) noexcept
{
assert(holding_db_lock());
assert(song != nullptr);

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
#include "util/Compiler.h"
#include "db/Visitor.hxx"
#include "db/PlaylistVector.hxx"
#include "db/Ptr.hxx"
#include "Song.hxx"
#include <boost/intrusive/list.hpp>
@@ -43,7 +44,6 @@ static constexpr unsigned DEVICE_INARCHIVE = -1;
static constexpr unsigned DEVICE_CONTAINER = -2;
class SongFilter;
class Database;
struct Directory {
static constexpr auto link_mode = boost::intrusive::normal_link;
@@ -96,21 +96,21 @@ struct Directory {
* If this is not nullptr, then this directory does not really
* exist, but is a mount point for another #Database.
*/
Database *mounted_database = nullptr;
DatabasePtr mounted_database;
public:
Directory(std::string &&_path_utf8, Directory *_parent);
~Directory();
Directory(std::string &&_path_utf8, Directory *_parent) noexcept;
~Directory() noexcept;
/**
* Create a new root #Directory object.
*/
gcc_malloc gcc_returns_nonnull
static Directory *NewRoot() {
static Directory *NewRoot() noexcept {
return new Directory(std::string(), nullptr);
}
bool IsMount() const {
bool IsMount() const noexcept {
return mounted_database != nullptr;
}
@@ -120,7 +120,7 @@ public:
*
* Caller must lock the #db_mutex.
*/
void Delete();
void Delete() noexcept;
/**
* Create a new #Directory object as a child of the given one.
@@ -129,7 +129,7 @@ public:
*
* @param name_utf8 the UTF-8 encoded name of the new sub directory
*/
Directory *CreateChild(const char *name_utf8);
Directory *CreateChild(const char *name_utf8) noexcept;
/**
* Caller must lock the #db_mutex.
@@ -149,7 +149,7 @@ public:
*
* Caller must lock the #db_mutex.
*/
Directory *MakeChild(const char *name_utf8) {
Directory *MakeChild(const char *name_utf8) noexcept {
Directory *child = FindChild(name_utf8);
if (child == nullptr)
child = CreateChild(name_utf8);
@@ -242,7 +242,7 @@ public:
* Add a song object to this directory. Its "parent" attribute must
* be set already.
*/
void AddSong(Song *song);
void AddSong(Song *song) noexcept;
/**
* Remove a song object from this directory (which effectively

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -42,6 +42,8 @@
#include "fs/FileSystem.hxx"
#include "util/CharUtil.hxx"
#include "util/Domain.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RecursiveMap.hxx"
#include "Log.hxx"
#ifdef ENABLE_ZLIB
@@ -60,8 +62,7 @@ inline SimpleDatabase::SimpleDatabase(const ConfigBlock &block)
#ifdef ENABLE_ZLIB
compress(block.GetBlockValue("compress", true)),
#endif
cache_path(block.GetPath("cache_directory")),
prefixed_light_song(nullptr)
cache_path(block.GetPath("cache_directory"))
{
if (path.IsNull())
throw std::runtime_error("No \"path\" parameter specified");
@@ -84,12 +85,12 @@ inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path,
prefixed_light_song(nullptr) {
}
Database *
DatabasePtr
SimpleDatabase::Create(EventLoop &, EventLoop &,
gcc_unused DatabaseListener &listener,
const ConfigBlock &block)
{
return new SimpleDatabase(block);
return std::make_unique<SimpleDatabase>(block);
}
void
@@ -219,6 +220,7 @@ SimpleDatabase::GetSong(const char *uri) const
prefixed_light_song =
new PrefixedLightSong(*song, r.directory->GetPath());
r.directory->mounted_database->ReturnSong(song);
return prefixed_light_song;
}
@@ -251,7 +253,7 @@ void
SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const noexcept
{
assert(song != nullptr);
assert(song == &light_song.Get() || song == prefixed_light_song);
assert(song == prefixed_light_song || song == &light_song.Get());
if (prefixed_light_song != nullptr) {
delete prefixed_light_song;
@@ -329,11 +331,11 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
"No such directory");
}
std::map<std::string, std::set<std::string>>
RecursiveMap<std::string>
SimpleDatabase::CollectUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType group) const
ConstBuffer<TagType> tag_types) const
{
return ::CollectUniqueTags(*this, selection, tag_type, group);
return ::CollectUniqueTags(*this, selection, tag_types);
}
DatabaseStats
@@ -390,13 +392,13 @@ SimpleDatabase::Save()
}
void
SimpleDatabase::Mount(const char *uri, Database *db)
SimpleDatabase::Mount(const char *uri, DatabasePtr db)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(uri != nullptr);
assert(db != nullptr);
#endif
assert(db != nullptr);
assert(*uri != 0);
ScopeDatabaseLock protect;
@@ -411,7 +413,7 @@ SimpleDatabase::Mount(const char *uri, Database *db)
"Parent not found");
Directory *mnt = r.directory->CreateChild(r.uri);
mnt->mounted_database = db;
mnt->mounted_database = std::move(db);
}
static constexpr bool
@@ -441,27 +443,21 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
#ifndef ENABLE_ZLIB
constexpr bool compress = false;
#endif
auto db = new SimpleDatabase(cache_path / name_fs,
compress);
try {
db->Open();
} catch (...) {
delete db;
throw;
}
auto db = std::make_unique<SimpleDatabase>(cache_path / name_fs,
compress);
db->Open();
// TODO: update the new database instance?
try {
Mount(local_uri, db);
Mount(local_uri, std::move(db));
} catch (...) {
db->Close();
delete db;
throw;
}
}
inline Database *
inline DatabasePtr
SimpleDatabase::LockUmountSteal(const char *uri) noexcept
{
ScopeDatabaseLock protect;
@@ -470,8 +466,7 @@ SimpleDatabase::LockUmountSteal(const char *uri) noexcept
if (r.uri != nullptr || !r.directory->IsMount())
return nullptr;
Database *db = r.directory->mounted_database;
r.directory->mounted_database = nullptr;
auto db = std::move(r.directory->mounted_database);
r.directory->Delete();
return db;
@@ -480,12 +475,11 @@ SimpleDatabase::LockUmountSteal(const char *uri) noexcept
bool
SimpleDatabase::Unmount(const char *uri) noexcept
{
Database *db = LockUmountSteal(uri);
auto db = LockUmountSteal(uri);
if (db == nullptr)
return false;
db->Close();
delete db;
return true;
}

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
#include "db/Interface.hxx"
#include "db/Ptr.hxx"
#include "fs/AllocatedPath.hxx"
#include "song/LightSong.hxx"
#include "util/Manual.hxx"
@@ -57,7 +58,7 @@ class SimpleDatabase : public Database {
* A buffer for GetSong() when prefixing the #LightSong
* instance from a mounted #Database.
*/
mutable PrefixedLightSong *prefixed_light_song;
mutable PrefixedLightSong *prefixed_light_song = nullptr;
/**
* A buffer for GetSong().
@@ -68,15 +69,14 @@ class SimpleDatabase : public Database {
mutable unsigned borrowed_song_count;
#endif
public:
SimpleDatabase(const ConfigBlock &block);
SimpleDatabase(AllocatedPath &&_path, bool _compress) noexcept;
public:
static Database *Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
static DatabasePtr Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block);
gcc_pure
Directory &GetRoot() noexcept {
@@ -99,7 +99,7 @@ public:
* success, this object gains ownership of the given #Database
*/
gcc_nonnull_all
void Mount(const char *uri, Database *db);
void Mount(const char *uri, DatabasePtr db);
/**
* Throws #std::runtime_error on error.
@@ -122,9 +122,8 @@ public:
VisitSong visit_song,
VisitPlaylist visit_playlist) const override;
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
TagType tag_type,
TagType group) const override;
RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
ConstBuffer<TagType> tag_types) const override;
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
@@ -142,7 +141,7 @@ private:
*/
void Load();
Database *LockUmountSteal(const char *uri) noexcept;
DatabasePtr LockUmountSteal(const char *uri) noexcept;
};
extern const DatabasePlugin simple_db_plugin;

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -40,10 +40,11 @@
#include "tag/Mask.hxx"
#include "fs/Traits.hxx"
#include "Log.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RecursiveMap.hxx"
#include "util/SplitString.hxx"
#include <string>
#include <set>
#include <assert.h>
#include <string.h>
@@ -82,10 +83,10 @@ public:
:Database(upnp_db_plugin),
event_loop(_event_loop) {}
static Database *Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block) noexcept;
static DatabasePtr Create(EventLoop &main_event_loop,
EventLoop &io_event_loop,
DatabaseListener &listener,
const ConfigBlock &block) noexcept;
void Open() override;
void Close() noexcept override;
@@ -97,9 +98,8 @@ public:
VisitSong visit_song,
VisitPlaylist visit_playlist) const override;
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
TagType tag_type,
TagType group) const override;
RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
ConstBuffer<TagType> tag_types) const override;
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
@@ -146,12 +146,12 @@ private:
const UPnPDirObject& dirent) const;
};
Database *
DatabasePtr
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
gcc_unused DatabaseListener &listener,
const ConfigBlock &) noexcept
{
return new UpnpDatabase(io_event_loop);
return std::make_unique<UpnpDatabase>(io_event_loop);
}
void
@@ -624,11 +624,11 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
helper.Commit();
}
std::map<std::string, std::set<std::string>>
RecursiveMap<std::string>
UpnpDatabase::CollectUniqueTags(const DatabaseSelection &selection,
TagType tag, TagType group) const
ConstBuffer<TagType> tag_types) const
{
return ::CollectUniqueTags(*this, selection, tag, group);
return ::CollectUniqueTags(*this, selection, tag_types);
}
DatabaseStats

@@ -94,7 +94,7 @@ UpdateService::CancelMount(const char *uri)
cancel_current = next.IsDefined() && next.storage == storage2;
}
if (auto *db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database)) {
if (auto *db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get())) {
queue.Erase(*db2);
cancel_current |= next.IsDefined() && next.db == db2;
}
@@ -188,7 +188,7 @@ UpdateService::Enqueue(const char *path, bool discard)
/* follow the mountpoint, update the mounted
database */
db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database);
db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get());
if (db2 == nullptr)
throw std::runtime_error("Cannot update this type of database");

@@ -320,6 +320,11 @@ public:
gcc_pure
bool IsCurrentSong(const DetachedSong &_song) const noexcept;
gcc_pure
bool IsUnseekableCurrentSong(const DetachedSong &_song) const noexcept {
return !seekable && IsCurrentSong(_song);
}
gcc_pure
bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept {
return seekable && IsCurrentSong(_song);

@@ -20,6 +20,8 @@
#include "config.h"
#include "DecoderList.hxx"
#include "DecoderPlugin.hxx"
#include "PluginUnavailable.hxx"
#include "Log.hxx"
#include "config/Data.hxx"
#include "config/Block.hxx"
#include "plugins/AudiofileDecoderPlugin.hxx"
@@ -45,6 +47,7 @@
#include "plugins/FluidsynthDecoderPlugin.hxx"
#include "plugins/SidplayDecoderPlugin.hxx"
#include "util/Macros.hxx"
#include "util/RuntimeError.hxx"
#include <string.h>
@@ -147,12 +150,22 @@ decoder_plugin_init_all(const ConfigData &config)
if (param != nullptr)
param->SetUsed();
if (plugin.Init(*param))
decoder_plugins_enabled[i] = true;
try {
if (plugin.Init(*param))
decoder_plugins_enabled[i] = true;
} catch (const PluginUnavailable &e) {
FormatError(e,
"Decoder plugin '%s' is unavailable",
plugin.name);
} catch (...) {
std::throw_with_nested(FormatRuntimeError("Failed to initialize decoder plugin '%s'",
plugin.name));
}
}
}
void decoder_plugin_deinit_all(void)
void
decoder_plugin_deinit_all() noexcept
{
decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
plugin.Finish();

@@ -40,11 +40,22 @@ decoder_plugin_init_all(const ConfigData &config);
/* this is where we "unload" all the "plugins" */
void
decoder_plugin_deinit_all();
decoder_plugin_deinit_all() noexcept;
class ScopeDecoderPluginsInit {
public:
explicit ScopeDecoderPluginsInit(const ConfigData &config) {
decoder_plugin_init_all(config);
}
~ScopeDecoderPluginsInit() noexcept {
decoder_plugin_deinit_all();
}
};
template<typename F>
static inline const DecoderPlugin *
decoder_plugins_find(F f)
decoder_plugins_find(F f) noexcept
{
for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))

@@ -33,6 +33,7 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
static constexpr Domain audiofile_domain("audiofile");

@@ -39,8 +39,8 @@ InitHybridDsdDecoder(const ConfigBlock &block)
without a DSD DAC, the PCM (=ALAC) part of the file is
better */
if (block.GetBlockParam("enabled") == nullptr) {
LogInfo(hybrid_dsd_domain,
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
LogDebug(hybrid_dsd_domain,
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
return false;
}

@@ -21,14 +21,13 @@
#include "MadDecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "input/InputStream.hxx"
#include "config/Block.hxx"
#include "tag/Id3Scan.hxx"
#include "tag/Id3ReplayGain.hxx"
#include "tag/Rva2.hxx"
#include "tag/Handler.hxx"
#include "tag/ReplayGain.hxx"
#include "tag/MixRamp.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Clamp.hxx"
#include "util/StringCompare.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -40,8 +39,6 @@
#include <id3tag.h>
#endif
#include <stdexcept>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
@@ -49,17 +46,17 @@
static constexpr unsigned long FRAMES_CUSHION = 2000;
enum mp3_action {
DECODE_SKIP = -3,
DECODE_BREAK = -2,
DECODE_CONT = -1,
DECODE_OK = 0
enum class MadDecoderAction {
SKIP,
BREAK,
CONT,
OK
};
enum muteframe {
MUTEFRAME_NONE,
MUTEFRAME_SKIP,
MUTEFRAME_SEEK
enum class MadDecoderMuteFrame {
NONE,
SKIP,
SEEK
};
/* the number of samples of silence the decoder inserts at start */
@@ -79,82 +76,86 @@ ToSongTime(mad_timer_t t) noexcept
}
static inline int32_t
mad_fixed_to_24_sample(mad_fixed_t sample)
mad_fixed_to_24_sample(mad_fixed_t sample) noexcept
{
static constexpr unsigned bits = 24;
static constexpr mad_fixed_t MIN = -MAD_F_ONE;
static constexpr mad_fixed_t MAX = MAD_F_ONE - 1;
/* round */
sample = sample + (1L << (MAD_F_FRACBITS - bits));
/* clip */
if (gcc_unlikely(sample > MAX))
sample = MAX;
else if (gcc_unlikely(sample < MIN))
sample = MIN;
/* quantize */
return sample >> (MAD_F_FRACBITS + 1 - bits);
return Clamp(sample, MAD_F_MIN, MAD_F_MAX)
>> (MAD_F_FRACBITS + 1 - bits);
}
static void
mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth,
unsigned int start, unsigned int end,
mad_fixed_to_24_buffer(int32_t *dest, const struct mad_pcm &src,
size_t start, size_t end,
unsigned int num_channels)
{
for (unsigned i = start; i < end; ++i)
for (size_t i = start; i < end; ++i)
for (unsigned c = 0; c < num_channels; ++c)
*dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]);
*dest++ = mad_fixed_to_24_sample(src.samples[c][i]);
}
static bool
mp3_plugin_init(const ConfigBlock &block)
mad_plugin_init(const ConfigBlock &block)
{
gapless_playback = block.GetBlockValue("gapless",
DEFAULT_GAPLESS_MP3_PLAYBACK);
return true;
}
struct MadDecoder {
class MadDecoder {
static constexpr size_t READ_BUFFER_SIZE = 40960;
static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048;
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
mad_timer_t timer;
unsigned char input_buffer[READ_BUFFER_SIZE];
int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE];
int32_t output_buffer[sizeof(mad_pcm::samples) / sizeof(mad_fixed_t)];
SignedSongTime total_time;
SongTime elapsed_time;
SongTime seek_time;
enum muteframe mute_frame = MUTEFRAME_NONE;
MadDecoderMuteFrame mute_frame = MadDecoderMuteFrame::NONE;
long *frame_offsets = nullptr;
mad_timer_t *times = nullptr;
unsigned long highest_frame = 0;
unsigned long max_frames = 0;
unsigned long current_frame = 0;
unsigned int drop_start_frames = 0;
unsigned int drop_end_frames = 0;
size_t highest_frame = 0;
size_t max_frames = 0;
size_t current_frame = 0;
unsigned int drop_start_frames;
unsigned int drop_end_frames;
unsigned int drop_start_samples = 0;
unsigned int drop_end_samples = 0;
bool found_replay_gain = false;
bool found_first_frame = false;
bool decoded_first_frame = false;
unsigned long bit_rate;
/**
* If this flag is true, then end-of-file was seen and a
* padding of 8 zero bytes were appended to #input_buffer, to
* allow libmad to decode the last frame.
*/
bool was_eof = false;
DecoderClient *const client;
InputStream &input_stream;
enum mad_layer layer = mad_layer(0);
MadDecoder(DecoderClient *client, InputStream &input_stream);
~MadDecoder();
public:
MadDecoder(DecoderClient *client, InputStream &input_stream) noexcept;
~MadDecoder() noexcept;
bool Seek(long offset);
bool FillBuffer();
void ParseId3(size_t tagsize, Tag *tag);
enum mp3_action DecodeNextFrameHeader(Tag *tag);
enum mp3_action DecodeNextFrame();
void RunDecoder() noexcept;
bool RunScan(TagHandler &handler) noexcept;
private:
bool Seek(long offset) noexcept;
bool FillBuffer() noexcept;
void ParseId3(size_t tagsize, Tag *tag) noexcept;
MadDecoderAction DecodeNextFrameHeader(Tag *tag) noexcept;
MadDecoderAction DecodeNextFrame() noexcept;
gcc_pure
offset_type ThisFrameOffset() const noexcept;
@@ -165,11 +166,11 @@ struct MadDecoder {
/**
* Attempt to calulcate the length of the song from filesize
*/
void FileSizeToSongLength();
void FileSizeToSongLength() noexcept;
bool DecodeFirstFrame(Tag *tag);
bool DecodeFirstFrame(Tag *tag) noexcept;
void AllocateBuffers() {
void AllocateBuffers() noexcept {
assert(max_frames > 0);
assert(frame_offsets == nullptr);
assert(times == nullptr);
@@ -179,27 +180,39 @@ struct MadDecoder {
}
gcc_pure
long TimeToFrame(SongTime t) const noexcept;
size_t TimeToFrame(SongTime t) const noexcept;
void UpdateTimerNextFrame();
/**
* Record the current frame's offset in the "frame_offsets"
* buffer and go forward to the next frame, updating the
* attributes "current_frame" and "timer".
*/
void UpdateTimerNextFrame() noexcept;
/**
* Sends the synthesized current frame via
* DecoderClient::SubmitData().
*/
DecoderCommand SendPCM(unsigned i, unsigned pcm_length);
DecoderCommand SubmitPCM(size_t start, size_t n) noexcept;
/**
* Synthesize the current frame and send it via
* DecoderClient::SubmitData().
*/
DecoderCommand SyncAndSend();
DecoderCommand SynthAndSubmit() noexcept;
bool Read();
/**
* @return false to stop decoding
*/
bool HandleCurrentFrame() noexcept;
bool LoadNextFrame() noexcept;
bool Read() noexcept;
};
MadDecoder::MadDecoder(DecoderClient *_client,
InputStream &_input_stream)
InputStream &_input_stream) noexcept
:client(_client), input_stream(_input_stream)
{
mad_stream_init(&stream);
@@ -210,7 +223,7 @@ MadDecoder::MadDecoder(DecoderClient *_client,
}
inline bool
MadDecoder::Seek(long offset)
MadDecoder::Seek(long offset) noexcept
{
try {
input_stream.LockSeek(offset);
@@ -225,32 +238,38 @@ MadDecoder::Seek(long offset)
}
inline bool
MadDecoder::FillBuffer()
MadDecoder::FillBuffer() noexcept
{
size_t remaining, length;
unsigned char *dest;
/* amount of rest data still residing in the buffer */
size_t rest_size = 0;
size_t max_read_size = sizeof(input_buffer);
unsigned char *dest = input_buffer;
if (stream.next_frame != nullptr) {
remaining = stream.bufend - stream.next_frame;
memmove(input_buffer, stream.next_frame, remaining);
dest = input_buffer + remaining;
length = READ_BUFFER_SIZE - remaining;
} else {
remaining = 0;
length = READ_BUFFER_SIZE;
dest = input_buffer;
rest_size = stream.bufend - stream.next_frame;
memmove(input_buffer, stream.next_frame, rest_size);
dest += rest_size;
max_read_size -= rest_size;
}
/* we've exhausted the read buffer, so give up!, these potential
* mp3 frames are way too big, and thus unlikely to be mp3 frames */
if (length == 0)
if (max_read_size == 0)
return false;
length = decoder_read(client, input_stream, dest, length);
if (length == 0)
return false;
size_t nbytes = decoder_read(client, input_stream,
dest, max_read_size);
if (nbytes == 0) {
if (was_eof || max_read_size < MAD_BUFFER_GUARD)
return false;
mad_stream_buffer(&stream, input_buffer, length + remaining);
was_eof = true;
nbytes = MAD_BUFFER_GUARD;
memset(dest, 0, nbytes);
}
mad_stream_buffer(&stream, input_buffer, rest_size + nbytes);
stream.error = MAD_ERROR_NONE;
return true;
@@ -286,7 +305,7 @@ parse_id3_mixramp(struct id3_tag *tag) noexcept
#endif
inline void
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag)
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
{
#ifdef ENABLE_ID3TAG
std::unique_ptr<id3_byte_t[]> allocated;
@@ -354,7 +373,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag)
* of the ID3 frame.
*/
static signed long
id3_tag_query(const void *p0, size_t length)
id3_tag_query(const void *p0, size_t length) noexcept
{
const char *p = (const char *)p0;
@@ -364,26 +383,26 @@ id3_tag_query(const void *p0, size_t length)
}
#endif /* !ENABLE_ID3TAG */
static enum mp3_action
RecoverFrameError(struct mad_stream &stream)
static MadDecoderAction
RecoverFrameError(const struct mad_stream &stream) noexcept
{
if (MAD_RECOVERABLE(stream.error))
return DECODE_SKIP;
return MadDecoderAction::SKIP;
else if (stream.error == MAD_ERROR_BUFLEN)
return DECODE_CONT;
return MadDecoderAction::CONT;
FormatWarning(mad_domain,
"unrecoverable frame level error: %s",
mad_stream_errorstr(&stream));
return DECODE_BREAK;
return MadDecoderAction::BREAK;
}
enum mp3_action
MadDecoder::DecodeNextFrameHeader(Tag *tag)
MadDecoderAction
MadDecoder::DecodeNextFrameHeader(Tag *tag) noexcept
{
if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
!FillBuffer())
return DECODE_BREAK;
return MadDecoderAction::BREAK;
if (mad_header_decode(&frame.header, &stream)) {
if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) {
@@ -393,7 +412,7 @@ MadDecoder::DecodeNextFrameHeader(Tag *tag)
if (tagsize > 0) {
ParseId3((size_t)tagsize, tag);
return DECODE_CONT;
return MadDecoderAction::CONT;
}
}
@@ -404,24 +423,24 @@ MadDecoder::DecodeNextFrameHeader(Tag *tag)
if (layer == (mad_layer)0) {
if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) {
/* Only layer 2 and 3 have been tested to work */
return DECODE_SKIP;
return MadDecoderAction::SKIP;
}
layer = new_layer;
} else if (new_layer != layer) {
/* Don't decode frames with a different layer than the first */
return DECODE_SKIP;
return MadDecoderAction::SKIP;
}
return DECODE_OK;
return MadDecoderAction::OK;
}
enum mp3_action
MadDecoder::DecodeNextFrame()
MadDecoderAction
MadDecoder::DecodeNextFrame() noexcept
{
if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
!FillBuffer())
return DECODE_BREAK;
return MadDecoderAction::BREAK;
if (mad_frame_decode(&frame, &stream)) {
if (stream.error == MAD_ERROR_LOSTSYNC) {
@@ -430,14 +449,14 @@ MadDecoder::DecodeNextFrame()
stream.this_frame);
if (tagsize > 0) {
mad_stream_skip(&stream, tagsize);
return DECODE_CONT;
return MadDecoderAction::CONT;
}
}
return RecoverFrameError(stream);
}
return DECODE_OK;
return MadDecoderAction::OK;
}
/* xing stuff stolen from alsaplayer, and heavily modified by jat */
@@ -476,7 +495,7 @@ struct lame {
};
static bool
parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) noexcept
{
int bitlen = *oldbitlen;
@@ -556,7 +575,7 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
}
static bool
parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
{
/* Unlike the xing header, the lame tag has a fixed length. Fail if
* not all 36 bytes (288 bits) are there. */
@@ -647,7 +666,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
}
static inline SongTime
mp3_frame_duration(const struct mad_frame *frame)
mad_frame_duration(const struct mad_frame *frame) noexcept
{
return ToSongTime(frame->header.duration);
}
@@ -672,12 +691,12 @@ MadDecoder::RestIncludingThisFrame() const noexcept
}
inline void
MadDecoder::FileSizeToSongLength()
MadDecoder::FileSizeToSongLength() noexcept
{
if (input_stream.KnownSize()) {
offset_type rest = RestIncludingThisFrame();
const SongTime frame_duration = mp3_frame_duration(&frame);
const SongTime frame_duration = mad_frame_duration(&frame);
const SongTime duration =
SongTime::FromScale<uint64_t>(rest,
frame.header.bitrate / 8);
@@ -694,25 +713,25 @@ MadDecoder::FileSizeToSongLength()
}
inline bool
MadDecoder::DecodeFirstFrame(Tag *tag)
MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
{
struct xing xing;
while (true) {
enum mp3_action ret;
MadDecoderAction ret;
do {
ret = DecodeNextFrameHeader(tag);
} while (ret == DECODE_CONT);
if (ret == DECODE_BREAK)
} while (ret == MadDecoderAction::CONT);
if (ret == MadDecoderAction::BREAK)
return false;
if (ret == DECODE_SKIP) continue;
if (ret == MadDecoderAction::SKIP) continue;
do {
ret = DecodeNextFrame();
} while (ret == DECODE_CONT);
if (ret == DECODE_BREAK)
} while (ret == MadDecoderAction::CONT);
if (ret == MadDecoderAction::BREAK)
return false;
if (ret == DECODE_OK) break;
if (ret == MadDecoderAction::OK) break;
}
struct mad_bitptr ptr = stream.anc_ptr;
@@ -724,7 +743,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
* if an xing tag exists, use that!
*/
if (parse_xing(&xing, &ptr, &bitlen)) {
mute_frame = MUTEFRAME_SKIP;
mute_frame = MadDecoderMuteFrame::SKIP;
if ((xing.flags & XING_FRAMES) && xing.frames) {
mad_timer_t duration = frame.header.duration;
@@ -736,9 +755,17 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
struct lame lame;
if (parse_lame(&lame, &ptr, &bitlen)) {
if (gapless_playback && input_stream.IsSeekable()) {
/* libmad inserts 529 samples of
silence at the beginning and
removes those 529 samples at the
end */
drop_start_samples = lame.encoder_delay +
DECODERDELAY;
drop_end_samples = lame.encoder_padding;
if (drop_end_samples > DECODERDELAY)
drop_end_samples -= DECODERDELAY;
else
drop_end_samples = 0;
}
/* Album gain isn't currently used. See comment in
@@ -767,7 +794,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
return true;
}
MadDecoder::~MadDecoder()
MadDecoder::~MadDecoder() noexcept
{
mad_synth_finish(&synth);
mad_frame_finish(&frame);
@@ -777,10 +804,10 @@ MadDecoder::~MadDecoder()
delete[] times;
}
long
size_t
MadDecoder::TimeToFrame(SongTime t) const noexcept
{
unsigned long i;
size_t i;
for (i = 0; i < highest_frame; ++i) {
auto frame_time = ToSongTime(times[i]);
@@ -792,12 +819,11 @@ MadDecoder::TimeToFrame(SongTime t) const noexcept
}
void
MadDecoder::UpdateTimerNextFrame()
MadDecoder::UpdateTimerNextFrame() noexcept
{
if (current_frame >= highest_frame) {
/* record this frame's properties in frame_offsets
(for seeking) and times */
bit_rate = frame.header.bitrate;
if (current_frame >= max_frames)
/* cap current_frame */
@@ -818,36 +844,22 @@ MadDecoder::UpdateTimerNextFrame()
}
DecoderCommand
MadDecoder::SendPCM(unsigned i, unsigned pcm_length)
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
{
unsigned max_samples = sizeof(output_buffer) /
sizeof(output_buffer[0]) /
MAD_NCHANNELS(&frame.header);
size_t num_samples = pcm_length - i;
while (i < pcm_length) {
unsigned int num_samples = pcm_length - i;
if (num_samples > max_samples)
num_samples = max_samples;
mad_fixed_to_24_buffer(output_buffer, synth.pcm,
i, i + num_samples,
MAD_NCHANNELS(&frame.header));
num_samples *= MAD_NCHANNELS(&frame.header);
i += num_samples;
mad_fixed_to_24_buffer(output_buffer, &synth,
i - num_samples, i,
MAD_NCHANNELS(&frame.header));
num_samples *= MAD_NCHANNELS(&frame.header);
auto cmd = client->SubmitData(input_stream, output_buffer,
sizeof(output_buffer[0]) * num_samples,
bit_rate / 1000);
if (cmd != DecoderCommand::NONE)
return cmd;
}
return DecoderCommand::NONE;
return client->SubmitData(input_stream, output_buffer,
sizeof(output_buffer[0]) * num_samples,
frame.header.bitrate / 1000);
}
inline DecoderCommand
MadDecoder::SyncAndSend()
MadDecoder::SynthAndSubmit() noexcept
{
mad_synth_frame(&synth, &frame);
@@ -864,33 +876,33 @@ MadDecoder::SyncAndSend()
drop_start_frames--;
return DecoderCommand::NONE;
} else if ((drop_end_frames > 0) &&
(current_frame == (max_frames + 1 - drop_end_frames))) {
current_frame == max_frames - drop_end_frames) {
/* stop decoding, effectively dropping all remaining
frames */
return DecoderCommand::STOP;
}
unsigned i = 0;
size_t i = 0;
if (!decoded_first_frame) {
i = drop_start_samples;
decoded_first_frame = true;
}
unsigned pcm_length = synth.pcm.length;
size_t pcm_length = synth.pcm.length;
if (drop_end_samples &&
(current_frame == max_frames - drop_end_frames)) {
current_frame == max_frames - drop_end_frames - 1) {
if (drop_end_samples >= pcm_length)
pcm_length = 0;
else
pcm_length -= drop_end_samples;
return DecoderCommand::STOP;
pcm_length -= drop_end_samples;
}
auto cmd = SendPCM(i, pcm_length);
auto cmd = SubmitPCM(i, pcm_length);
if (cmd != DecoderCommand::NONE)
return cmd;
if (drop_end_samples &&
(current_frame == max_frames - drop_end_frames))
current_frame == max_frames - drop_end_frames - 1)
/* stop decoding, effectively dropping
* all remaining samples */
return DecoderCommand::STOP;
@@ -899,44 +911,51 @@ MadDecoder::SyncAndSend()
}
inline bool
MadDecoder::Read()
MadDecoder::HandleCurrentFrame() noexcept
{
UpdateTimerNextFrame();
switch (mute_frame) {
DecoderCommand cmd;
case MUTEFRAME_SKIP:
mute_frame = MUTEFRAME_NONE;
case MadDecoderMuteFrame::SKIP:
mute_frame = MadDecoderMuteFrame::NONE;
break;
case MUTEFRAME_SEEK:
case MadDecoderMuteFrame::SEEK:
if (elapsed_time >= seek_time)
mute_frame = MUTEFRAME_NONE;
mute_frame = MadDecoderMuteFrame::NONE;
UpdateTimerNextFrame();
break;
case MUTEFRAME_NONE:
cmd = SyncAndSend();
case MadDecoderMuteFrame::NONE:
cmd = SynthAndSubmit();
UpdateTimerNextFrame();
if (cmd == DecoderCommand::SEEK) {
assert(input_stream.IsSeekable());
const auto t = client->GetSeekTime();
unsigned long j = TimeToFrame(t);
size_t j = TimeToFrame(t);
if (j < highest_frame) {
if (Seek(frame_offsets[j])) {
current_frame = j;
was_eof = false;
client->CommandFinished();
} else
client->SeekError();
} else {
seek_time = t;
mute_frame = MUTEFRAME_SEEK;
mute_frame = MadDecoderMuteFrame::SEEK;
client->CommandFinished();
}
} else if (cmd != DecoderCommand::NONE)
return false;
}
return true;
}
inline bool
MadDecoder::LoadNextFrame() noexcept
{
while (true) {
enum mp3_action ret;
MadDecoderAction ret;
do {
Tag tag;
@@ -945,84 +964,104 @@ MadDecoder::Read()
if (!tag.IsEmpty())
client->SubmitTag(input_stream,
std::move(tag));
} while (ret == DECODE_CONT);
if (ret == DECODE_BREAK)
} while (ret == MadDecoderAction::CONT);
if (ret == MadDecoderAction::BREAK)
return false;
const bool skip = ret == DECODE_SKIP;
const bool skip = ret == MadDecoderAction::SKIP;
if (mute_frame == MUTEFRAME_NONE) {
if (mute_frame == MadDecoderMuteFrame::NONE) {
do {
ret = DecodeNextFrame();
} while (ret == DECODE_CONT);
if (ret == DECODE_BREAK)
} while (ret == MadDecoderAction::CONT);
if (ret == MadDecoderAction::BREAK)
return false;
}
if (!skip && ret == DECODE_OK)
if (!skip && ret == MadDecoderAction::OK)
return true;
}
}
static void
mp3_decode(DecoderClient &client, InputStream &input_stream)
inline bool
MadDecoder::Read() noexcept
{
MadDecoder data(&client, input_stream);
return HandleCurrentFrame() &&
LoadNextFrame();
}
inline void
MadDecoder::RunDecoder() noexcept
{
assert(client != nullptr);
Tag tag;
if (!data.DecodeFirstFrame(&tag)) {
if (client.GetCommand() == DecoderCommand::NONE)
if (!DecodeFirstFrame(&tag)) {
if (client->GetCommand() == DecoderCommand::NONE)
LogError(mad_domain,
"input/Input does not appear to be a mp3 bit stream");
"input does not appear to be a mp3 bit stream");
return;
}
data.AllocateBuffers();
AllocateBuffers();
client.Ready(CheckAudioFormat(data.frame.header.samplerate,
SampleFormat::S24_P32,
MAD_NCHANNELS(&data.frame.header)),
input_stream.IsSeekable(),
data.total_time);
client->Ready(CheckAudioFormat(frame.header.samplerate,
SampleFormat::S24_P32,
MAD_NCHANNELS(&frame.header)),
input_stream.IsSeekable(),
total_time);
if (!tag.IsEmpty())
client.SubmitTag(input_stream, std::move(tag));
client->SubmitTag(input_stream, std::move(tag));
while (data.Read()) {}
while (Read()) {}
}
static bool
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
static void
mad_decode(DecoderClient &client, InputStream &input_stream)
{
MadDecoder data(nullptr, is);
if (!data.DecodeFirstFrame(nullptr))
MadDecoder data(&client, input_stream);
data.RunDecoder();
}
inline bool
MadDecoder::RunScan(TagHandler &handler) noexcept
{
if (!DecodeFirstFrame(nullptr))
return false;
if (!data.total_time.IsNegative())
handler.OnDuration(SongTime(data.total_time));
if (!total_time.IsNegative())
handler.OnDuration(SongTime(total_time));
try {
handler.OnAudioFormat(CheckAudioFormat(data.frame.header.samplerate,
handler.OnAudioFormat(CheckAudioFormat(frame.header.samplerate,
SampleFormat::S24_P32,
MAD_NCHANNELS(&data.frame.header)));
MAD_NCHANNELS(&frame.header)));
} catch (...) {
}
return true;
}
static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr };
static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr };
static bool
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
{
MadDecoder data(nullptr, is);
return data.RunScan(handler);
}
static const char *const mad_suffixes[] = { "mp3", "mp2", nullptr };
static const char *const mad_mime_types[] = { "audio/mpeg", nullptr };
const struct DecoderPlugin mad_decoder_plugin = {
"mad",
mp3_plugin_init,
mad_plugin_init,
nullptr,
mp3_decode,
mad_decode,
nullptr,
nullptr,
mad_decoder_scan_stream,
nullptr,
mp3_suffixes,
mp3_mime_types,
mad_suffixes,
mad_mime_types,
};

@@ -208,10 +208,12 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
TagBuilder tag_builder;
AddTagHandler h(tag_builder);
if (ScanOpusTags(packet.packet, packet.bytes, &rgi, h) &&
!tag_builder.empty()) {
client.SubmitReplayGain(&rgi);
if (!ScanOpusTags(packet.packet, packet.bytes, &rgi, h))
return;
client.SubmitReplayGain(&rgi);
if (!tag_builder.empty()) {
Tag tag = tag_builder.Commit();
auto cmd = client.SubmitTag(input_stream, std::move(tag));
if (cmd != DecoderCommand::NONE)

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_OPUS_READER_HXX
#define MPD_OPUS_READER_HXX
#include "util/StringView.hxx"
#include <algorithm>
#include <stdint.h>
@@ -81,18 +83,16 @@ public:
return ReadWord(length) && Skip(length);
}
char *ReadString() {
StringView ReadString() {
uint32_t length;
if (!ReadWord(length) || length >= 65536)
if (!ReadWord(length))
return nullptr;
const char *src = (const char *)Read(length);
if (src == nullptr)
return nullptr;
char *dest = new char[length + 1];
*std::copy_n(src, length, dest) = 0;
return dest;
return {src, length};
}
};

@@ -22,10 +22,12 @@
#include "lib/xiph/XiphTags.hxx"
#include "tag/Handler.hxx"
#include "tag/ParseName.hxx"
#include "util/ASCII.hxx"
#include "ReplayGainInfo.hxx"
#include <string>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
gcc_pure
@@ -44,7 +46,7 @@ ScanOneOpusTag(const char *name, const char *value,
ReplayGainInfo *rgi,
TagHandler &handler) noexcept
{
if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) {
if (rgi != nullptr && StringEqualsCaseASCII(name, "R128_TRACK_GAIN")) {
/* R128_TRACK_GAIN is a Q7.8 fixed point number in
dB */
@@ -52,7 +54,8 @@ ScanOneOpusTag(const char *name, const char *value,
long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0)
rgi->track.gain = double(l) / 256.;
} else if (rgi != nullptr && strcmp(name, "R128_ALBUM_GAIN") == 0) {
} else if (rgi != nullptr &&
StringEqualsCaseASCII(name, "R128_ALBUM_GAIN")) {
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
dB */
@@ -91,18 +94,25 @@ ScanOpusTags(const void *data, size_t size,
return false;
while (n-- > 0) {
char *p = r.ReadString();
if (p == nullptr)
const auto s = r.ReadString();
if (s == nullptr)
return false;
char *eq = strchr(p, '=');
if (eq != nullptr && eq > p) {
*eq = 0;
if (s.size >= 4096)
continue;
ScanOneOpusTag(p, eq + 1, rgi, handler);
}
const auto eq = s.Find('=');
if (eq == nullptr || eq == s.data)
continue;
delete[] p;
auto name = s, value = s;
name.SetEnd(eq);
value.MoveFront(eq + 1);
const std::string name2(name.data, name.size);
const std::string value2(value.data, value.size);
ScanOneOpusTag(name2.c_str(), value2.c_str(), rgi, handler);
}
return true;

@@ -20,18 +20,18 @@
#include "WildmidiDecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "tag/Handler.hxx"
#include "util/Domain.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringFormat.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "fs/Path.hxx"
#include "Log.hxx"
#include "PluginUnavailable.hxx"
extern "C" {
#include <wildmidi_lib.h>
}
static constexpr Domain wildmidi_domain("wildmidi");
static constexpr AudioFormat wildmidi_audio_format{48000, SampleFormat::S16, 2};
static bool
@@ -43,14 +43,27 @@ wildmidi_init(const ConfigBlock &block)
if (!FileExists(path)) {
const auto utf8 = path.ToUTF8();
FormatDebug(wildmidi_domain,
"configuration file does not exist: %s",
utf8.c_str());
return false;
throw PluginUnavailable(StringFormat<1024>("configuration file does not exist: %s",
utf8.c_str()));
}
return WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
0) == 0;
#ifdef LIBWILDMIDI_VERSION
/* WildMidi_ClearError() requires libwildmidi 0.4 */
WildMidi_ClearError();
AtScopeExit() { WildMidi_ClearError(); };
#endif
if (WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
0) != 0) {
#ifdef LIBWILDMIDI_VERSION
/* WildMidi_GetError() requires libwildmidi 0.4 */
throw PluginUnavailable(WildMidi_GetError());
#else
throw PluginUnavailable("WildMidi_Init() failed");
#endif
}
return true;
}
static void

@@ -36,8 +36,9 @@ if flac_dep.found()
]
endif
conf.set('ENABLE_VORBIS_DECODER', libvorbis_dep.found())
if libvorbis_dep.found()
conf.set('ENABLE_VORBIS_DECODER', libvorbis_dep.found() or libvorbisidec_dep.found())
conf.set('HAVE_TREMOR', libvorbisidec_dep.found())
if libvorbis_dep.found() or libvorbisidec_dep.found()
decoder_plugins_sources += [
'VorbisDecoderPlugin.cxx',
'VorbisDomain.cxx',
@@ -181,6 +182,7 @@ decoder_plugins = static_library(
libsidplay_dep,
libsndfile_dep,
libvorbis_dep,
libvorbisidec_dep,
ogg_dep,
wavpack_dep,
wildmidi_dep,

@@ -110,15 +110,9 @@ BufferedSocket::OnSocketReady(unsigned flags) noexcept
if (flags & READ) {
assert(!input.IsFull());
if (!ReadToBuffer())
if (!ReadToBuffer() || !ResumeInput())
return false;
if (!ResumeInput())
/* we must return "true" here or
SocketMonitor::Dispatch() will call
Cancel() on a freed object */
return true;
if (!input.IsFull())
ScheduleRead();
}

@@ -46,6 +46,11 @@ public:
using SocketMonitor::Close;
private:
/**
* @return the number of bytes read from the socket, 0 if the
* socket isn't ready for reading, -1 on error (the socket has
* been closed and probably destructed)
*/
ssize_t DirectRead(void *data, size_t length) noexcept;
/**

@@ -80,7 +80,7 @@ private:
void
BlockingCall(EventLoop &loop, std::function<void()> &&f)
{
if (loop.IsDead() || loop.IsInside()) {
if (!loop.IsAlive() || loop.IsInside()) {
/* we're already inside the loop - we can simply call
the function */
f();

@@ -45,6 +45,11 @@ public:
}
private:
/**
* @return the number of bytes written to the socket, 0 if the
* socket isn't ready for writing, -1 on error (the socket has
* been closed and probably destructed)
*/
ssize_t DirectWrite(const void *data, size_t length) noexcept;
protected:

@@ -25,7 +25,13 @@
EventLoop::EventLoop(ThreadId _thread)
:SocketMonitor(*this),
quit(false), dead(false),
/* if this instance is hosted by an EventThread (no ThreadId
known yet) then we're not yet alive until the thread is
started; for the main EventLoop instance, we assume it's
already alive, because nobody but EventThread will call
SetAlive() */
alive(!_thread.IsNull()),
quit(false),
thread(_thread)
{
SocketMonitor::Open(SocketDescriptor(wake_fd.Get()));
@@ -143,12 +149,11 @@ EventLoop::Run() noexcept
assert(IsInside());
assert(!quit);
assert(!dead);
assert(alive);
assert(busy);
SocketMonitor::Schedule(SocketMonitor::READ);
AtScopeExit(this) {
dead = true;
SocketMonitor::Cancel();
};
@@ -215,7 +220,6 @@ EventLoop::Run() noexcept
} while (!quit);
#ifndef NDEBUG
assert(!dead);
assert(busy);
assert(thread.IsInside());
#endif

@@ -85,12 +85,15 @@ class EventLoop final : SocketMonitor
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
std::atomic_bool quit;
/**
* If this is true, then Run() has returned.
* Is this #EventLoop alive, i.e. can events be scheduled?
* This is used by BlockingCall() to determine whether
* schedule in the #EventThread or to call directly (if
* there's no #EventThread yet/anymore).
*/
std::atomic_bool dead;
bool alive;
std::atomic_bool quit;
/**
* True when the object has been modified and another check is
@@ -207,9 +210,12 @@ private:
bool OnSocketReady(unsigned flags) noexcept override;
public:
gcc_pure
bool IsDead() const noexcept {
return dead;
void SetAlive(bool _alive) noexcept {
alive = _alive;
}
bool IsAlive() const noexcept {
return alive;
}
/**

@@ -392,7 +392,29 @@ ServerSocket::AddPath(AllocatedPath &&path)
#else /* !HAVE_UN */
(void)path;
throw std::runtime_error("UNIX domain socket support is disabled");
throw std::runtime_error("Local socket support is disabled");
#endif /* !HAVE_UN */
}
void
ServerSocket::AddAbstract(const char *name)
{
#if !defined(__linux__)
(void)name;
throw std::runtime_error("Abstract sockets are only available on Linux");
#elif !defined(HAVE_UN)
(void)name;
throw std::runtime_error("Local socket support is disabled");
#else
assert(name != nullptr);
assert(*name == '@');
AllocatedSocketAddress address;
address.SetLocal(name);
AddAddress(std::move(address));
#endif
}

@@ -90,7 +90,7 @@ public:
void AddHost(const char *hostname, unsigned port);
/**
* Add a listener on a Unix domain socket.
* Add a listener on a local socket.
*
* Throws #std::runtime_error on error.
*
@@ -99,6 +99,16 @@ public:
*/
void AddPath(AllocatedPath &&path);
/**
* Add a listener on an abstract local socket (Linux specific).
*
* Throws on error.
*
* @param name the abstract socket name, starting with a '@'
* instead of a null byte
*/
void AddAbstract(const char *name);
/**
* Add a socket descriptor that is accepting connections. After this
* has been called, don't call server_socket_open(), because the

@@ -33,8 +33,8 @@ SocketMonitor::Dispatch(unsigned flags) noexcept
{
flags &= GetScheduledFlags();
if (flags != 0 && !OnSocketReady(flags) && IsDefined())
Cancel();
if (flags != 0)
OnSocketReady(flags);
}
SocketMonitor::~SocketMonitor() noexcept

@@ -26,8 +26,11 @@
void
EventThread::Start()
{
assert(!event_loop.IsAlive());
assert(!thread.IsDefined());
event_loop.SetAlive(true);
thread.Start();
}
@@ -35,6 +38,9 @@ void
EventThread::Stop() noexcept
{
if (thread.IsDefined()) {
assert(event_loop.IsAlive());
event_loop.SetAlive(false);
event_loop.Break();
thread.Join();
}

@@ -65,7 +65,7 @@ public:
/**
* Flush pending data and return it. This should be called
* repepatedly until it returns nullptr.
* repeatedly until it returns nullptr.
*/
virtual ConstBuffer<void> Flush();
};

@@ -56,6 +56,7 @@ public:
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
ConstBuffer<void> Flush() override;
};
class PreparedAutoConvertFilter final : public PreparedFilter {
@@ -104,6 +105,18 @@ AutoConvertFilter::FilterPCM(ConstBuffer<void> src)
return filter->FilterPCM(src);
}
ConstBuffer<void>
AutoConvertFilter::Flush()
{
if (convert != nullptr) {
auto result = convert->Flush();
if (!result.IsNull())
return filter->FilterPCM(result);
}
return filter->Flush();
}
std::unique_ptr<PreparedFilter>
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept
{

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2018 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -88,4 +88,13 @@ private:
#endif
};
template<typename F>
void
WithBufferedOutputStream(OutputStream &os, F &&f)
{
BufferedOutputStream bos(os);
f(bos);
bos.Flush();
}
#endif

@@ -31,8 +31,33 @@
#include "system/Error.hxx"
#include "util/StringFormat.hxx"
#ifdef __linux__
#include <fcntl.h>
#endif
#ifdef __linux__
FileOutputStream::FileOutputStream(FileDescriptor _directory_fd,
Path _path, Mode _mode)
:path(_path),
directory_fd(_directory_fd),
mode(_mode)
{
Open();
}
#endif
FileOutputStream::FileOutputStream(Path _path, Mode _mode)
:path(_path), mode(_mode)
:path(_path),
#ifdef __linux__
directory_fd(AT_FDCWD),
#endif
mode(_mode)
{
Open();
}
inline void
FileOutputStream::Open()
{
switch (mode) {
case Mode::CREATE:
@@ -149,8 +174,12 @@ FileOutputStream::Cancel() noexcept
* Open a file using Linux's O_TMPFILE for writing the given file.
*/
static bool
OpenTempFile(FileDescriptor &fd, Path path)
OpenTempFile(FileDescriptor directory_fd,
FileDescriptor &fd, Path path)
{
if (directory_fd != FileDescriptor(AT_FDCWD))
return fd.Open(directory_fd, ".", O_TMPFILE|O_WRONLY, 0666);
const auto directory = path.GetDirectoryName();
if (directory.IsNull())
return false;
@@ -165,11 +194,15 @@ FileOutputStream::OpenCreate(bool visible)
{
#ifdef HAVE_O_TMPFILE
/* try Linux's O_TMPFILE first */
is_tmpfile = !visible && OpenTempFile(fd, GetPath());
is_tmpfile = !visible && OpenTempFile(directory_fd, fd, GetPath());
if (!is_tmpfile) {
#endif
/* fall back to plain POSIX */
if (!fd.Open(GetPath().c_str(),
if (!fd.Open(
#ifdef __linux__
directory_fd,
#endif
GetPath().c_str(),
O_WRONLY|O_CREAT|O_TRUNC,
0666))
throw FormatErrno("Failed to create %s",
@@ -188,7 +221,11 @@ FileOutputStream::OpenAppend(bool create)
if (create)
flags |= O_CREAT;
if (!fd.Open(path.c_str(), flags))
if (!fd.Open(
#ifdef __linux__
directory_fd,
#endif
path.c_str(), flags))
throw FormatErrno("Failed to append to %s",
path.c_str());
}
@@ -219,12 +256,12 @@ FileOutputStream::Commit()
#ifdef HAVE_O_TMPFILE
if (is_tmpfile) {
unlink(GetPath().c_str());
unlinkat(directory_fd.Get(), GetPath().c_str(), 0);
/* hard-link the temporary file to the final path */
if (linkat(AT_FDCWD,
StringFormat<64>("/proc/self/fd/%d", fd.Get()),
AT_FDCWD, path.c_str(),
directory_fd.Get(), path.c_str(),
AT_SYMLINK_FOLLOW) < 0)
throw FormatErrno("Failed to commit %s",
path.c_str());
@@ -253,7 +290,11 @@ FileOutputStream::Cancel() noexcept
#ifdef HAVE_O_TMPFILE
if (!is_tmpfile)
#endif
unlink(GetPath().c_str());
#ifdef __linux__
unlinkat(directory_fd.Get(), GetPath().c_str(), 0);
#else
unlink(GetPath().c_str());
#endif
break;
case Mode::CREATE_VISIBLE:

@@ -59,6 +59,10 @@ class Path;
class FileOutputStream final : public OutputStream {
const AllocatedPath path;
#ifdef __linux__
const FileDescriptor directory_fd;
#endif
#ifdef _WIN32
HANDLE handle = INVALID_HANDLE_VALUE;
#else
@@ -108,6 +112,11 @@ private:
public:
explicit FileOutputStream(Path _path, Mode _mode=Mode::CREATE);
#ifdef __linux__
FileOutputStream(FileDescriptor _directory_fd, Path _path,
Mode _mode=Mode::CREATE);
#endif
~FileOutputStream() noexcept {
if (IsDefined())
Cancel();
@@ -133,6 +142,7 @@ public:
private:
void OpenCreate(bool visible);
void OpenAppend(bool create);
void Open();
bool Close() noexcept {
assert(IsDefined());

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014-2018 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -27,8 +27,8 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef MPD_OUTPUT_STREAM_HXX
#define MPD_OUTPUT_STREAM_HXX
#ifndef OUTPUT_STREAM_HXX
#define OUTPUT_STREAM_HXX
#include <stddef.h>

@@ -39,7 +39,7 @@ class StdioOutputStream final : public OutputStream {
FILE *const file;
public:
explicit StdioOutputStream(FILE *_file):file(_file) {}
explicit StdioOutputStream(FILE *_file) noexcept:file(_file) {}
/* virtual methods from class OutputStream */
void Write(const void *data, size_t size) override {

3
src/haiku/add_resources.sh Executable file

@@ -0,0 +1,3 @@
#!/bin/sh
cp "$2" "$1" && xres -o "$1" -- "$3" && mimeset -f "$1" || (rm -f "$1"; exit 1)

@@ -1,18 +1,26 @@
rc = meson.find_program('rc')
xres = meson.find_program('xres')
haiku_conf = configuration_data()
haiku_conf.set('VERSION', meson.project_version())
splitted_version = meson.project_version().split('~')[0].split('.')
haiku_conf.set('VERSION_MAJOR', splitted_version[0])
haiku_conf.set('VERSION_MINOR', splitted_version.get(1, '0'))
haiku_conf.set('VERSION_REVISION', splitted_version.get(2, '0'))
haiku_conf.set('VERSION_EXTRA', splitted_version.get(3, '0'))
mpd_rdef = configure_file(
input: 'mpd.rdef.in',
output: 'mpd.rdef',
configuration: haiku_conf,
)
rc = find_program('rc')
xres = find_program('xres')
rsrc = custom_target(
'mpd.rsrc',
output: 'mpd.rsrc',
input: 'mpd.rdef',
input: mpd_rdef,
command: [rc, '-o', '@OUTPUT@', '@INPUT@'],
)
custom_target(
'mpd.rsrc',
output: 'mpd',
input: [mpd, rsrc],
command: [xres, '-o', '@OUTPUT@', '--', '@INPUT@'],
install: true,
install_dir: get_option('bindir'),
)
addres = files('add_resources.sh')

@@ -2,7 +2,15 @@ resource app_signature "application/x-vnd.MusicPD";
resource app_flags B_BACKGROUND_APP;
// TODO: resource app_version {};
resource app_version {
major = @VERSION_MAJOR@,
middle = @VERSION_MINOR@,
minor = @VERSION_REVISION@,
variety = B_APPV_ALPHA,
internal = @VERSION_EXTRA@,
short_info = "Music Player Daemon @VERSION@",
long_info = "Music Player Daemon @VERSION@ ©The Music Player Daemon Project"
};
resource vector_icon {
$"6E6369661F050102031604BEE29BBEC5403EC540BEE29B4A10004A10000001C6"

@@ -59,6 +59,9 @@ BufferedInputStream::~BufferedInputStream() noexcept
void
BufferedInputStream::Check()
{
if (read_error)
std::rethrow_exception(read_error);
if (input)
input->Check();
}
@@ -66,6 +69,11 @@ BufferedInputStream::Check()
void
BufferedInputStream::Seek(offset_type new_offset)
{
if (new_offset >= size) {
offset = size;
return;
}
auto r = buffer.Read(new_offset);
if (r.HasData()) {
/* nice, we already have some data at the desired
@@ -96,7 +104,7 @@ BufferedInputStream::IsEOF() noexcept
bool
BufferedInputStream::IsAvailable() noexcept
{
return IsEOF() || buffer.Read(offset).HasData();
return IsEOF() || buffer.Read(offset).HasData() || read_error;
}
size_t
@@ -159,6 +167,32 @@ BufferedInputStream::RunThread() noexcept
idle = false;
seek = false;
client_cond.signal();
} else if (!idle && !read_error &&
offset != input->GetOffset() &&
!IsAvailable()) {
/* a past Seek() call was a no-op because data
was already available at that position, but
now we've reached a new position where
there is no more data in the buffer, and
our input is reading somewhere else (maybe
stuck at the end of the file); to find a
way out, we now seek our input to our
reading position to be able to fill our
buffer */
try {
input->Seek(offset);
} catch (...) {
/* this is really a seek error, but we
register it as a read_error,
because seek_error is only checked
by Seek(), and at our frontend (our
own InputStream interface) is in
"read" mode */
read_error = std::current_exception();
client_cond.signal();
InvokeOnAvailable();
}
} else if (!idle && !read_error &&
input->IsAvailable() && !input->IsEOF()) {
const auto read_offset = input->GetOffset();

@@ -35,4 +35,16 @@ input_stream_global_init(const ConfigData &config, EventLoop &event_loop);
void
input_stream_global_finish() noexcept;
class ScopeInputPluginsInit {
public:
ScopeInputPluginsInit(const ConfigData &config,
EventLoop &event_loop) {
input_stream_global_init(config, event_loop);
}
~ScopeInputPluginsInit() noexcept {
input_stream_global_finish();
}
};
#endif

@@ -23,6 +23,7 @@
#include "config.h"
#include "CdioParanoiaInputPlugin.hxx"
#include "lib/cdio/Paranoia.hxx"
#include "../InputStream.hxx"
#include "../InputPlugin.hxx"
#include "util/TruncateString.hxx"
@@ -41,18 +42,12 @@
#include <stdlib.h>
#include <assert.h>
#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
#include <cdio/paranoia/paranoia.h>
#else
#include <cdio/paranoia.h>
#endif
#include <cdio/cd_types.h>
class CdioParanoiaInputStream final : public InputStream {
cdrom_drive_t *const drv;
CdIo_t *const cdio;
cdrom_paranoia_t *const para;
CdromParanoia para;
const lsn_t lsn_from, lsn_to;
int lsn_relofs;
@@ -66,18 +61,17 @@ class CdioParanoiaInputStream final : public InputStream {
bool reverse_endian,
lsn_t _lsn_from, lsn_t _lsn_to)
:InputStream(_uri, _mutex),
drv(_drv), cdio(_cdio), para(cdio_paranoia_init(drv)),
drv(_drv), cdio(_cdio), para(drv),
lsn_from(_lsn_from), lsn_to(_lsn_to),
lsn_relofs(0),
buffer_lsn(-1)
{
/* Set reading mode for full paranoia, but allow
skipping sectors. */
paranoia_modeset(para,
PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
/* seek to beginning of the track */
cdio_paranoia_seek(para, lsn_from, SEEK_SET);
para.Seek(lsn_from);
seekable = true;
size = (lsn_to - lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
@@ -90,7 +84,7 @@ class CdioParanoiaInputStream final : public InputStream {
}
~CdioParanoiaInputStream() {
cdio_paranoia_free(para);
para = {};
cdio_cddap_close_no_free_cdio(drv);
cdio_destroy(cdio);
}
@@ -208,10 +202,10 @@ input_cdio_open(const char *uri,
throw std::runtime_error("Unable to identify audio CD disc.");
}
cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
cdio_cddap_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
if (speed > 0) {
FormatDebug(cdio_domain,"Attempting to set CD speed to %dx",speed);
cdda_speed_set(drv,speed);
cdio_cddap_speed_set(drv,speed);
}
if (0 != cdio_cddap_open(drv)) {
@@ -277,7 +271,7 @@ CdioParanoiaInputStream::Seek(offset_type new_offset)
{
const ScopeUnlock unlock(mutex);
cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET);
para.Seek(lsn_from + lsn_relofs);
}
}
@@ -285,56 +279,53 @@ size_t
CdioParanoiaInputStream::Read(void *ptr, size_t length)
{
size_t nbytes = 0;
int diff;
size_t len, maxwrite;
int16_t *rbuf;
char *s_err, *s_mess;
char *wptr = (char *) ptr;
while (length > 0) {
/* end of track ? */
if (lsn_from + lsn_relofs > lsn_to)
break;
//current sector was changed ?
const int16_t *rbuf;
if (lsn_relofs != buffer_lsn) {
const ScopeUnlock unlock(mutex);
rbuf = cdio_paranoia_read(para, nullptr);
try {
rbuf = para.Read().data;
} catch (...) {
char *s_err = cdio_cddap_errors(drv);
if (s_err) {
FormatError(cdio_domain,
"paranoia_read: %s", s_err);
#if LIBCDIO_VERSION_NUM >= 90
cdio_cddap_free_messages(s_err);
#else
free(s_err);
#endif
}
s_err = cdda_errors(drv);
if (s_err) {
FormatError(cdio_domain,
"paranoia_read: %s", s_err);
free(s_err);
throw;
}
s_mess = cdda_messages(drv);
if (s_mess) {
free(s_mess);
}
if (!rbuf)
throw std::runtime_error("paranoia read error");
//store current buffer
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
buffer_lsn = lsn_relofs;
} else {
//use cached sector
rbuf = (int16_t *)buffer;
rbuf = (const int16_t *)buffer;
}
//correct offset
diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
len = (length < maxwrite? length : maxwrite);
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
const size_t len = (length < maxwrite? length : maxwrite);
//skip diff bytes from this lsn
memcpy(wptr, ((char*)rbuf) + diff, len);
memcpy(wptr, ((const char *)rbuf) + diff, len);
//update pointer
wptr += len;
nbytes += len;

@@ -306,9 +306,8 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block)
{
try {
curl_init = new CurlInit(event_loop);
} catch (const std::runtime_error &e) {
LogError(e);
throw PluginUnavailable(e.what());
} catch (...) {
std::throw_with_nested(PluginUnavailable("CURL initialization failed"));
}
const auto version_info = curl_version_info(CURLVERSION_FIRST);

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