Compare commits

..

228 Commits

Author SHA1 Message Date
Max Kellermann
f591193dda release v0.23.6 2022-03-14 18:55:47 +01:00
Max Kellermann
434869900e android/build.py: fix typo in error message
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1379
2022-03-14 18:49:50 +01:00
Max Kellermann
2aed7378cc TagAny: support CUE tracks
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1482
2022-03-14 18:42:31 +01:00
Max Kellermann
71cd6e6248 lib/xiph/meson.build: define FLAC__NO_DLL for static libFLAC build (Windows)
In libFLAC 0.3.4 (commit c9530118a4), the "dllimport" check has been
changed from "_MSC_VER" to "_WIN32", and now the MPD build is affected
by it.

Defining FLAC__NO_DLL disables the use of "dllimport", which allows
linking properly to the static libFLAC build.
2022-03-14 15:08:59 +01:00
Max Kellermann
c83294916a python/build/libs.py: update Boost to 1.78.0 2022-03-14 14:52:24 +01:00
Max Kellermann
603bbe0afd python/build/libs.py: update libnfs to 5.0.1 2022-03-14 14:52:24 +01:00
Max Kellermann
c361e235eb python/build/libs.py: update CURL to 7.82.0 2022-03-14 14:52:24 +01:00
Max Kellermann
8a59493d96 python/build/libs.py: update OpenSSL to 3.0.1 2022-03-14 14:50:06 +01:00
Max Kellermann
7ef86cbf9f python/build/libs.py: update FFmpeg to 5.0 2022-03-14 14:50:06 +01:00
Max Kellermann
c9530118a4 python/build/libs.py: update FLAC to 1.3.4 2022-03-14 14:31:13 +01:00
Max Kellermann
878d9abeb7 python/build/libs.py: update libogg to 1.3.5 2022-03-14 14:29:59 +01:00
Max Kellermann
2d705efe1c python/build/libs.py: update libmpdclient to 2.20 2022-03-14 14:29:22 +01:00
Richard Schorrig
aeaef85507 WasapiOutputPlugin pause bug fix
Wasapi output plugin won't start playing after being paused

The cause is that the scope guard in the WASAPI work thread
(WasapiOutputPlugin.cxx, function WasapiOutputThread::Work(), in the
while (true) loop) is set up too 'late' in the execution. There is one
condition ("if (data_in_frames >= buffer_size_in_frames)") when it is
hit, the loop will continue without executing the scope guard. This
scope guard is responsible for emptying the buffer again, and if the
buffer is not emptied, the above mentioned condition will stay true.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1451
2022-03-14 14:26:00 +01:00
nick black
ebae25d175 plugins/FfmpegIO: include libavutil/mem.h
ffmpeg from current git master no longer exposes
av_malloc() nor av_free() through other included
headers. directly include libavutil/mem.h to fix
compilation with (as-yet-unreleased) ffmpeg.
2022-03-14 14:11:31 +01:00
jcorporation
5ad1a01d7a Remove bmp, tiff and add webp for coverimage filenames
- supporting bmp and tiff seems outdated
- webp is more widely used for coverimages
2022-03-14 14:09:23 +01:00
Max Kellermann
8f84e1befd decoder/plugins/FfmpegIo: return AVERROR_EOF at end of file
This part of the AVIOContext API is not documented :-(

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1448
2022-03-14 14:00:28 +01:00
Max Kellermann
9975905faf output/PipeWire: initialize field "stream" in Open()
Must be initialized for the check in SetVolume().
2022-03-09 14:29:46 +01:00
Max Kellermann
233184568c doc/protocol.rst: describe the FILTER argument to playlist{find,search} 2022-02-14 09:11:41 +01:00
Wolfgang Müller
59da778009 doc/user.rst: Clarify how MPD reads metadata
The writing and reading of metadata involves lots of different programs
and libraries. Therefore it is prudent to point out how exactly MPD
receives metadata. Ideally this helps to point users to the right place
if their tags are not picked up correctly.
2022-02-14 09:11:11 +01:00
Max Kellermann
108ce95b7c android/Receiver: fix indent 2022-01-26 14:43:47 +01:00
Max Kellermann
86e9ed5f3a decoder/opus: fix "readpicture" on Opus files
Don't return early from ScanOpusTags() if only
TagHandler::WantPicture() is set.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1413
2022-01-26 14:43:45 +01:00
Sam Bazley
fbecb05bf4 Fix Android build error: needs_exe_wrapper
lib/src/libmpdclient-2.19/meson.build:1:0: ERROR: Unknown options: "needs_exe_wrapper"

The "needs_exe_wrapper" option was incorrectly set under
[built-in options] rather than [properties].
2022-01-11 20:33:48 +01:00
Sam Bazley
4983703375 Android: Detect output change with ACTION_AUDIO_BECOMING_NOISY
Improves the changes made in 57687779be by
using AudioManager.ACTION_AUDIO_BECOMING_NOISY rather than listening for
wired headset unplug events or Bluetooth headset disconnect events. This
method is more flexible, allowing the feature to work on other types of
audio output device, as well as Bluetooth devices that don't set their
device class correctly. This change also has the benefit of being more
responsive, pausing the audio before it is rerouted to the built-in
speaker.

https://developer.android.com/guide/topics/media-apps/volume-and-earphones
2022-01-04 16:42:53 +01:00
aeolio
3856224df9 lib/alsa/Error: add missing #include 2021-12-15 11:14:38 +01:00
aeolio
6d4bedfc56 lib/alsa/Error: fix typo 2021-12-15 11:14:34 +01:00
Max Kellermann
bea821f194 doc/user.rst: add MixRamp documentation 2021-12-06 21:32:39 +01:00
Rosen Penev
4e276256c0 more braced init list conversion
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-12-06 09:16:04 +01:00
Tim Siegel
d0f9062b56 mpdconf.example: fix a few spelling typos 2021-12-05 22:58:45 +01:00
Max Kellermann
b9cc036703 .github/workflows/build.yml: rebuild branch v0.23.x 2021-12-03 23:00:42 +01:00
Max Kellermann
4e9b88559b SingleMode: convert "pure" to "const" 2021-12-03 16:09:34 +01:00
Max Kellermann
3452682a42 IcyMetaDataParser: move to tag/ 2021-12-03 16:07:39 +01:00
Max Kellermann
9262b24504 AudioCompress: move to pcm/ 2021-12-03 16:04:59 +01:00
Max Kellermann
a5fa43b526 fs/io: move to io/ 2021-12-03 14:35:41 +01:00
Max Kellermann
8681a3d74c replace TextFile references with LineReader 2021-12-03 14:22:56 +01:00
Max Kellermann
f9c4d88b12 fs/io/TextFile: add interface LineReader 2021-12-03 14:20:29 +01:00
Max Kellermann
799032505e io/uring/Queue: add method RequireSubmitEntry()
Fixes assertion failure when the submit queue is empty.
2021-12-03 13:58:39 +01:00
Max Kellermann
c8f174ac92 io/uring/Operation: disallow copying 2021-12-03 13:52:04 +01:00
Max Kellermann
047e169f3e util/BindMethod: merge MakeBind{Method,Function}Wrapper(), they are identical now 2021-12-03 13:51:56 +01:00
Max Kellermann
687327c9e8 util/BindMethod: merge structs {Method,Function}SignatureHelper into one 2021-12-03 13:51:56 +01:00
Max Kellermann
26dc37bd76 util/BindMethod: merge structs {Method,Function}WrapperGenerator into one 2021-12-03 13:51:55 +01:00
Max Kellermann
c693e4aa64 util/BindMethod: remove unused struct MethodWithSignature 2021-12-03 13:51:55 +01:00
Max Kellermann
acab731fef util/BindMethod: simplify MakeBindFunctionWrapper() 2021-12-03 13:51:55 +01:00
Max Kellermann
7e4ba3cb72 util/BindMethod: add MethodSignatureHelper::function_pointer 2021-12-03 13:51:55 +01:00
Max Kellermann
172c4d9c7d util/BindMethod: remove unnecessary template arguments from BindMethodWrapperGenerator 2021-12-03 13:51:55 +01:00
Max Kellermann
bd5f6cbc7b util/BindMethod: simplify more templates using "auto" template arguments 2021-12-03 13:51:55 +01:00
Max Kellermann
6fcd1c734b util/BindMethod: eliminate struct BindMethodWrapperGenerator2 2021-12-03 13:51:55 +01:00
Max Kellermann
eca097dbfb util/BindMethod: simplify more templates using "auto" template arguments 2021-12-03 13:51:55 +01:00
Max Kellermann
51ffafa011 util/BindMethod: use std::remove_reference_t 2021-12-03 13:51:25 +01:00
Max Kellermann
8dca602346 util/BindMethod: simplify BindMethod() 2021-12-03 13:51:18 +01:00
Max Kellermann
0ed24f3a05 util/IntrusiveList: disallow copying IntrusiveListHook 2021-12-03 13:50:05 +01:00
Max Kellermann
e25e0030e7 increment version number to 0.23.6 2021-12-01 20:01:22 +01:00
Max Kellermann
df4b6b92f2 release v0.23.5 2021-12-01 20:00:00 +01:00
Max Kellermann
1c69913eca decoder/flac: submit MixRamp only if there is actual data 2021-12-01 17:58:51 +01:00
Max Kellermann
cb5c6259fd decoder/mad: submit MixRamp only if there is actual data
Fixes MixRamp failures when a MP3 file has two ID3 tags, one of them
without MixRamp.
2021-12-01 17:19:53 +01:00
Max Kellermann
bf287fefb5 decoder/mad: move parse_id3_mixramp() to tag/Id3MixRamp.cxx 2021-12-01 17:11:36 +01:00
Max Kellermann
20bf1d68e6 MixRampInfo: move to tag/ 2021-12-01 17:09:02 +01:00
Max Kellermann
9bc4c168fd tag/MixRamp: rename to MixRampParser.cxx 2021-12-01 17:07:53 +01:00
Max Kellermann
3415049d1c test/tag/TestMixRampParser: include the header, not the .cxx file 2021-12-01 17:07:39 +01:00
Max Kellermann
a45949b597 tag/MixRamp: [[gnu::...]] attributes 2021-12-01 15:48:33 +01:00
Max Kellermann
6009d4abab tag/MixRamp: use std::string_view 2021-12-01 15:47:54 +01:00
Max Kellermann
16fb843c9b tag/MixRamp: fix typo which broken MixRamp
Fixes regression by commit 8e0d810968 which is 2 years old, and nobody
noticed.  D'oh, how embarassing!
2021-12-01 15:46:31 +01:00
Max Kellermann
36b333459b test/tag/TestMixRampParser: new unit test 2021-12-01 15:46:01 +01:00
Max Kellermann
4d3320233e test/test_mixramp: move to test/tag/ 2021-12-01 15:33:17 +01:00
Max Kellermann
933a1a41e6 lib/upnp/Discovery: use InjectEvent instead of DeferEvent
Fixes regression by commit 774b4313f2
2021-11-30 18:03:27 +01:00
August2111
1ff8626716 MSVC util/StringAPI.hxx add usage of MSVC compiler 2021-11-26 17:30:17 +01:00
Max Kellermann
c30466b84a net/IPv4Address: add method GetPortBE() 2021-11-26 16:25:43 +01:00
Max Kellermann
868f1a4431 net/UniqueSocketDescriptor, ...: include <utility> instead of <algorithm>
Since C++11, std::swap() lives in <utility>.
2021-11-26 16:25:29 +01:00
Max Kellermann
05f529fffd util/StringStrip: use [[gnu::...]] attributes 2021-11-26 16:24:55 +01:00
Max Kellermann
f01388559f .github/workflows/build.yml: fix the ccache.key 2021-11-26 13:32:48 +01:00
Max Kellermann
27edd4a610 .github/workflows: merge build-{linux,macos}.yml into one 2021-11-26 13:32:08 +01:00
Max Kellermann
cc421b04cd test/meson.build: add "protocol:gtest" where appropriate 2021-11-26 08:47:06 +01:00
Max Kellermann
3f2bc325a1 test/meson.build: fix test() indent 2021-11-26 08:40:40 +01:00
Max Kellermann
54686dfd79 test/meson.build: add dependencies on run_input
Fixes spurious unit test failures because run_input has not yet been
built.
2021-11-26 08:35:49 +01:00
Rosen Penev
f22cf02ed8 fix wrong namespace name
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-26 08:08:45 +01:00
Rosen Penev
5b51d0f733 use some auto
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-26 08:08:45 +01:00
Rosen Penev
e03f82636a const reference conversion
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-23 12:33:03 -08:00
Rosen Penev
d53d85bd79 remove unused includes
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-23 12:33:03 -08:00
Max Kellermann
4682ae0898 command/database: support relative offsets for "searchadd"
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1337
2021-11-23 12:17:32 +01:00
Max Kellermann
fd5b195879 .github/workflows/build-macos.yml: use actions/setup-python@v1
Without it, BSFishy/meson-build defaults to /usr/local/bin/python,
which is Python 2.
2021-11-23 12:17:32 +01:00
Max Kellermann
bb5df9839d .github/workflows/build-macos.yml: install Meson, ninja and Boost 2021-11-23 12:17:32 +01:00
Max Kellermann
be34d55291 .github/workflows: add macOS build 2021-11-23 11:41:40 +01:00
Max Kellermann
c13911b171 .github/workflows: auto-build with GitHub Actions 2021-11-23 10:45:14 +01:00
Max Kellermann
6f83bdd6f3 Merge branch '1' of git://github.com/neheb/MPD 2021-11-23 10:39:07 +01:00
Rosen Penev
9bcd425a85 array conversions
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-23 01:38:10 -08:00
Max Kellermann
ec917f70d2 Merge remote-tracking branches 'neheb/2' and 'neheb/3' 2021-11-23 09:23:43 +01:00
Rosen Penev
40ce4eeb43 use cinttypes header
stdint.h is deprecated.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-22 23:30:36 -08:00
Rosen Penev
29ae84e199 manual braced init
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-22 23:30:04 -08:00
Rosen Penev
250011f016 return by braced init list
shorter

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-22 23:28:08 -08:00
Max Kellermann
e08c85ae2d doc/mpd.conf.5.rst: move ReplayGain documentation to user.rst 2021-11-22 22:25:04 +01:00
Max Kellermann
dcb5ca203c db/DatabasePlaylist: increment only one variable
Fixes "searchaddpl" bug emitting bogus error "Bad position".

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1338
2021-11-22 20:47:34 +01:00
Max Kellermann
77df5a8f24 lib/pcre: migrate to PCRE2
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1352
2021-11-22 19:32:45 +01:00
kaliko
d6bebd2507 doc/conf.py: Set sidebar width to 300px to limit wrapping
This enhances readability in sidebar, especially for "User’s Manual" and
"Protocol" pages
2021-11-20 10:49:15 +01:00
Max Kellermann
f74996c02f Merge remote-tracking branches 'neheb/1', 'neheb/2', 'neheb/3', 'neheb/4' and 'neheb/5' 2021-11-20 07:55:24 +01:00
Max Kellermann
eea2d35d3a util/AllocatedString, ...: add missing include for std::exchange()
Fixes building with GCC 12.
2021-11-19 16:06:20 +01:00
Max Kellermann
d94e8bd82d queue/IdTable: include cleanup 2021-11-19 16:03:09 +01:00
Max Kellermann
b0c92e1a34 queue/IdTable: lazy-initialize the "data" array
With large "max_playlist_length" settings, the "data" array can be
very large, and initializing it during MPD startup causes page faults,
resulting in allocation of physical RAM.  This commit postpones the
initialization until the queue is really large, to avoid wasting
memory.
2021-11-19 16:00:39 +01:00
Max Kellermann
ead5bcf048 queue/IdTable: make size const 2021-11-19 15:51:10 +01:00
kaliko
bdd268a524 doc/user.rst: update build dependencies on Debian Bullseye 2021-11-19 11:04:47 +01:00
Shen-Ta Hsieh
e783c2bd2c util/LazyRandomEngine: use std::optional to avoid allocation
Signed-off-by: Shen-Ta Hsieh <ibmibmibm.tw@gmail.com>
2021-11-14 03:53:42 +08:00
Rosen Penev
837fc98638 use const references
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-11 17:18:33 -08:00
Rosen Penev
5deca66fdc add various nodiscard
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-11 17:17:26 -08:00
Rosen Penev
cfe2dd4147 use nullptr
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-11 17:16:19 -08:00
Rosen Penev
00f8d65a17 remove std::move
clang-tidy reports this is trivially copyable and thus std::move has no
effect.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-11 17:15:21 -08:00
Rosen Penev
4e0e4c00bf treewide: replace lock_guard with scoped_lock
SonarLint reports the latter to be better:

std::scoped_lock basically provides the same feature as std::lock_guard,
but is more generic: It can lock several mutexes at the same time, with a
deadlock prevention mechanism (see {rule:cpp:S5524}). The equivalent code
to perform simultaneous locking with std::lock_guard is significantly more
complex. Therefore, it is simpler to use std::scoped_lock all the time,
even when locking only one mutex (there will be no performance impact).

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-11 17:13:03 -08:00
Max Kellermann
a8c77a6fba Merge branch '1' of git://github.com/neheb/MPD 2021-11-11 10:33:17 +01:00
Rosen Penev
31aa6d0c4f use auto with make_unique
C arrays can be used with make_unique in C++17.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-11 01:33:03 -08:00
Max Kellermann
d051c4931d Merge branch '2' of git://github.com/neheb/MPD 2021-11-11 10:32:45 +01:00
Rosen Penev
94b0baceb0 convert address_family_ranking to std::array
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-11-11 01:27:31 -08:00
Max Kellermann
16feb261e2 increment version number to 0.23.5 2021-11-11 10:18:19 +01:00
Max Kellermann
f084bf7872 release v0.23.4 2021-11-11 10:16:36 +01:00
Max Kellermann
1112d3907a Revert "systemd: add "RuntimeDirectory" directive"
This reverts commit 552c30eae4.

It has caused various problems; for example, MPD wasn't able to write
the pid_file (which was already mitigated by commit a4e4217204).

And apparently, the socket file created in the same directory by
mpd.socket disappears when mpd.service (re)creates the directory.  I
could not reproduce this problem with 247.3, but maybe this is a bug
in older systemd versions?

Until we figure out why this happens, let's remove the
RuntimeDirectory directive.  A future MPD version may be launched as
regular user, not as root, which will eliminate one major problem with
RuntimeDirectory.
2021-11-11 10:16:13 +01:00
Max Kellermann
3464497880 command/database: add optional position parameter to "searchaddpl"
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1328
2021-11-11 09:52:49 +01:00
Max Kellermann
651f57bced command/playlist: save only if at least one song was added 2021-11-11 09:50:31 +01:00
Max Kellermann
b4e72aba6c command/playlist: move code to SearchInsertIntoPlaylist() 2021-11-11 09:40:41 +01:00
0xC0ncord
061dd2dfef output/plugins: fix build error with clang and -stdlib=libc++
This fixes this build error observed with clang and -stdlib=libc++:

../mpd-0.23.3/src/output/plugins/PipeWireOutputPlugin.cxx:661:55: error: implicit instantiation of undefined template 'std::array<std::byte, 64>'
        std::array<std::byte, MAX_CHANNELS * MAX_INTERLEAVE> buffer;
                                                             ^
/usr/include/c++/v1/__tuple:219:64: note: template is declared here
template <class _Tp, size_t _Size> struct _LIBCPP_TEMPLATE_VIS array;
                                                               ^
2021-11-10 15:35:56 -05:00
Max Kellermann
5f4ec7de5b decoder/ffmpeg, lib/ffmpeg: make AVCodec pointers "const"
For libavcodec 59 support.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1333
2021-11-09 21:09:14 +01:00
Max Audron
6f81bb4b09 upnp: add option to configure interface for db plugin
Add an option to the UPnP database plugin to configure which interface
is used by upnp to discover servers.

upnp by default selects the first interface that is not loopback, which
in some cases might not be the desired interface. For example if wanting
to access a DLNA server over a VPN connection.

The "interface" option can now be set to the name of the desired
interface to achieve this.

The default behaviour remains unchanged.
2021-11-08 23:04:07 +01:00
Max Audron
4ed60a5711 upnp: expose interface configuration on UpnpInit2()
Adds the Interface Name as an argument to the *Init functions to make it
possible to select which interface is used by upnp to detect servers.

Currently "nullptr" is passed in to let the upnp library select an
interface, as before.
2021-11-08 22:53:01 +01:00
Max Kellermann
c93195c94b NEWS: fix typo 2021-11-05 14:45:43 +01:00
Max Kellermann
f30adac4bb doc/mpdconf.example: add comments recommending not to use log_file and pid_file 2021-11-05 09:06:27 +01:00
Max Kellermann
a4e4217204 Main: ignore the "pid_file" setting if started as systemd service
Commit 552c30eae caused problems for those people who still had a
"pid_file" setting (even though that is obsolete with systemd),
because now /run/mpd is owned by root:root (our mpd.service has no
User=mpd directive, so systemd starts MPD as root).

To work around this problem, and to be able to keep
RuntimeDirectory=mpd (which solved a problem of other MPD users), the
best compromise seems to just ignore the "pid_file" setting when it is
of no use.
2021-11-05 09:02:56 +01:00
Max Kellermann
8754d705a1 CommandLine: rename struct options 2021-11-05 08:57:12 +01:00
Max Kellermann
23d4a2d6a5 Main: pass struct options by reference 2021-11-05 08:56:05 +01:00
Max Kellermann
ce77b148d9 CommandLine: add option --systemd
This way, MPD can reliably detect whether it was started as systemd
service, which is better than checking sd_booted(), which only checks
whether systemd manages all services, but still MPD could be started
manually.
2021-11-05 08:51:49 +01:00
Max Kellermann
be3eca39e8 NEWS: add missing lines 2021-11-04 17:59:02 +01:00
Max Kellermann
3413b1aeb4 output/alsa: add option thesycon_dsd_workaround 2021-11-04 17:55:53 +01:00
Max Kellermann
356d13e9dd lib/alsa/HwSetup: add missing include 2021-11-04 17:55:15 +01:00
Max Kellermann
fa34bf0aaf Merge branch 'feature/win32-disable-openmpt123' of git://github.com/ibmibmibm/MPD 2021-11-04 15:11:26 +01:00
Max Kellermann
5d0941476a lib/alsa/Error: a std::system_error category for libasound errors 2021-11-04 14:59:00 +01:00
Max Kellermann
5ff0bbd0f8 lib/fmt/AudioFormatFormatter: add formatter for SampleFormat 2021-11-04 14:55:01 +01:00
Shen-Ta Hsieh
a3764e533c python/build/libs.py: disable building libopenmpt cli
Signed-off-by: Shen-Ta Hsieh <ibmibmibm.tw@gmail.com>
2021-11-04 21:54:12 +08:00
Shen-Ta Hsieh
3e05cba30e python/build/libs.py: update libopenmpt configure flags
Signed-off-by: Shen-Ta Hsieh <ibmibmibm.tw@gmail.com>
2021-11-04 15:23:24 +08:00
Max Kellermann
14b3c0f0af event/Loop: destruct the Uring::Manager in the destructor before assert()
Fixes assertion failure when the EventLoop gets destructed before
Run() was ever called.

Fixes https://bugs.debian.org/998310
2021-11-03 18:32:14 +01:00
Max Kellermann
67aff05051 increment version number to 0.23.4 2021-10-31 18:17:35 +01:00
Max Kellermann
19a101c3ac release v0.23.3 2021-10-31 18:13:10 +01:00
Max Kellermann
8da17a8211 doc/user.rst: add optimized build options to examples 2021-10-31 17:09:16 +01:00
Max Kellermann
2748929039 doc/user.rst: add -Dwrap_mode=forcefallback to Android/Windows examples 2021-10-31 17:08:59 +01:00
Max Kellermann
0c900a4bfa doc/user.rst: pass -Dandroid_debug_keystore=... to ./android/build.py 2021-10-31 17:03:37 +01:00
Max Kellermann
f1d5d70010 android/run-javac.sh: switch to Java 7 2021-10-31 16:55:40 +01:00
Max Kellermann
56ebc7637d python/build/libs.py: update FFmpeg to 4.4.1 2021-10-31 16:44:11 +01:00
Max Kellermann
996dd9fc8b python/build/libs.py: update libopenmpt to 0.5.12 2021-10-31 16:42:50 +01:00
Max Kellermann
056514d598 output/snapcast: reset unflushed_input after successful read
With the "wave" encoder, this has no effect, but it's more correct.
2021-10-31 16:35:42 +01:00
Max Kellermann
9a21bdfd6a output/snapcast: implement Pause()
This uncomments the code which had been present already in the first
Snapcast commit (copied from the "httpd" output plugin), but I
commented it because I did not know whether I needed to send silence
samples to all Snapcast clients.

As a side effect, this fixes playback when no Snapcast client is
connected; this was broken because Pause() always returned a positive
value when there were no clients.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1310
2021-10-31 16:26:29 +01:00
Max Kellermann
03f99dd26e db/update/Walk: use GetFilenameSuffix() instead of uri_get_suffix()
Unlike GetFilenameSuffix(), uri_get_suffix() removes the query string
first, which breaks file names with question marks in the name.
Therefore, uri_get_suffix() shall only be applied to remote URIs.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1316
2021-10-31 13:18:24 +01:00
Max Kellermann
bfb1b641f9 db/update/InotifyUpdate: fix use-after-free bug
Regression by commit 2d8847f428
2021-10-28 13:39:38 +02:00
Naglis Jonaitis
72ba98c464 doc/protocol.rst: add missing backtick 2021-10-27 02:11:39 +03:00
Max Kellermann
dcd19c0592 config/Path: use StringView::Split() 2021-10-26 12:55:01 +02:00
Max Kellermann
109159e0f7 Permission: use StringView::Split() 2021-10-26 12:25:47 +02:00
Max Kellermann
409b877eea output/ao: include cleanup 2021-10-26 12:20:18 +02:00
Max Kellermann
c5bf7948ff fs/StandardDirectory: use the RUNTIME_DIRECTORY environment variable 2021-10-26 09:30:16 +02:00
Max Kellermann
b9f7127691 fs/StandardDirectory: add GetAppRuntimeDir() 2021-10-26 09:30:16 +02:00
Max Kellermann
1e6f5f012c fs/StandardDirectory: add GetUserRuntimeDir() 2021-10-26 09:30:16 +02:00
Max Kellermann
225d85fd9b fs/StandardDirectory: use "if" with initializer 2021-10-26 09:29:57 +02:00
Max Kellermann
1bb22f118d fs/StandardDirectory: add more pure/const attributes 2021-10-26 09:04:20 +02:00
Max Kellermann
552c30eae4 systemd: add "RuntimeDirectory" directive 2021-10-26 08:38:36 +02:00
Max Kellermann
48e8a26813 command/playlist: allow range in playlistdelete 2021-10-25 12:23:37 +02:00
Max Kellermann
ade847bc89 PlaylistFile: fold spl_move_index() into handle_playlistmove() 2021-10-25 12:13:45 +02:00
Max Kellermann
a6173e0eae command/playlist: add position parameter to "playlistadd"
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1106
2021-10-25 12:10:47 +02:00
Max Kellermann
4529bb4a83 doc/protocol.rst: add "since" version notes 2021-10-25 08:47:23 +02:00
Max Kellermann
258ecb764f PlaylistFile: add class PlaylistFileEditor 2021-10-23 13:54:50 +02:00
Max Kellermann
6f595e9abb command/queue: add optional position parameter to "add"
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1285
2021-10-23 13:12:44 +02:00
Max Kellermann
35c4c7e8bf command/queue: move #ifdef out of AddDatabaseSelection() 2021-10-23 13:09:04 +02:00
Max Kellermann
293ed924d1 command/queue: pass Partition to AddDatabaseSelection() 2021-10-23 13:06:31 +02:00
Max Kellermann
c8121176b3 output/alsa: add option "stop_dsd_silence" to work around DSD DAC noise 2021-10-23 12:25:32 +02:00
Max Kellermann
ee270f9b00 meson.build: log_dep is only needed internally 2021-10-23 12:08:43 +02:00
Max Kellermann
bf1d77a4d8 output/alsa: un-inline several methods 2021-10-23 12:02:27 +02:00
Max Kellermann
a9344fafe9 lib/alsa/AllowedFormat: use StringView::RemoveSuffix() 2021-10-23 11:43:31 +02:00
Max Kellermann
b8890726f2 lib/alsa/AllowedFormat: use std::string_view 2021-10-23 11:42:30 +02:00
Max Kellermann
0f84332654 output/alsa: make "mode" const 2021-10-23 11:39:59 +02:00
Max Kellermann
46c82259f7 output/Control: make config fields const 2021-10-22 20:22:22 +02:00
Max Kellermann
2d03823283 output/Control: fold Configure() into the constructor 2021-10-22 20:21:58 +02:00
Max Kellermann
bba144eca5 output/Control: use C++ initializers 2021-10-22 20:21:43 +02:00
Max Kellermann
9af73dad93 output/Multiple: remove unused method Add() 2021-10-22 20:21:35 +02:00
Max Kellermann
f0d66bf6a6 output/Control: pass rvalue reference to move constructor 2021-10-22 20:14:37 +02:00
Max Kellermann
5ad53a7554 output/Thread: remove duplicate code by calling InternalCloseOutput() 2021-10-22 19:54:47 +02:00
Max Kellermann
7b2e3331f2 output/Filtered: improve API docs 2021-10-22 19:54:38 +02:00
Max Kellermann
3cb44f6652 increment version number to 0.23.3 2021-10-22 12:50:11 +02:00
Max Kellermann
b7fdff46f2 release v0.23.2 2021-10-22 12:45:45 +02:00
Max Kellermann
e16109330d input/last: clear "uri" in OnCloseTimer()
Without clearing the "uri" field, the next Open() call attempts to
reuse the old InputStream, but it has already been closed, so Open()
always returns nullptr.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1300
2021-10-22 12:45:18 +02:00
Max Kellermann
72621531e0 protocol/Result: convert to Client method 2021-10-22 11:55:39 +02:00
Max Kellermann
0a48146efc client/Client: pass std::string_view to Write()
Almost all callers have string literal, and the length is known at
compile time.
2021-10-22 11:54:14 +02:00
Max Kellermann
0c4bf12bfd player/CrossFade: fix inverted check and wrong variable
The inverted check was introduced by commit 46d00dd85f, and commit
8ad17d25ef added a check for the wrong variable.  D'oh!

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1303
2021-10-22 11:49:38 +02:00
Max Kellermann
b8e0855ef3 output/pipewire: obey PipeWire's DSD bit order and interleave
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1297
2021-10-21 21:15:16 +02:00
Max Kellermann
6467502b9d output/pipewire: restore SampleFormat::DSD after ToPipeWireAudioFormat() call 2021-10-21 21:15:13 +02:00
Max Kellermann
15b67f20e5 output/pipewire: un-inline ParamChanged() 2021-10-21 20:11:22 +02:00
Max Kellermann
0825179f00 output/pipewire: add local reference variables 2021-10-21 20:02:59 +02:00
Max Kellermann
97211d0aad output/pipewire: rename field "buffer" to "pod_buffer" 2021-10-21 20:02:32 +02:00
Max Kellermann
029c499bfa output/pipewire: use std::fill_n() 2021-10-21 20:01:44 +02:00
Max Kellermann
0ba867ec16 output/pipewire: use MAX_CHANNELS, not SPA_AUDIO_MAX_CHANNELS
MPD supports only 8 channels, so MAX_CHANNELS is enough, the array
doens't need to be SPA_AUDIO_MAX_CHANNELS (which is 64).
2021-10-21 20:01:01 +02:00
Max Kellermann
866d147122 output/pipewire: make field "channels" unsigned 2021-10-21 19:59:48 +02:00
Max Kellermann
32851d1bc7 output/pipewire: DSD support
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1297
2021-10-20 11:39:54 +02:00
Max Kellermann
78257408b4 output/pipewire: report errors from the "state_changed" callback 2021-10-20 11:24:57 +02:00
Max Kellermann
f447b7615e output/pipewire: check pw_stream_connect() errors 2021-10-20 11:24:51 +02:00
Max Kellermann
1f780b7209 output/Thread: log exception details 2021-10-20 11:24:51 +02:00
Max Kellermann
04bf8a6b1a output/pipewire: fix memory leak in SendTag() 2021-10-20 10:16:36 +02:00
Max Kellermann
c4c64854d4 output/pipewire: evaluate errno after libpipewire function calls 2021-10-20 10:13:27 +02:00
Max Kellermann
17562dc90b output/pipewire: remove misplaced noexcept 2021-10-20 09:41:27 +02:00
Max Kellermann
7b24316734 output/pipewire: fix coding style 2021-10-20 09:41:10 +02:00
Max Kellermann
5fab107fd3 lib/nfs/FileReader: use the thread-safe InjectEvent
.. instead of DeferEvent, which is not thread-safe.  This caused
various playback problems, which was initially caused by the
DeferEvent/InjectEvent split in commit 774b4313f2

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1298
2021-10-20 09:38:09 +02:00
Max Kellermann
f31920e092 event/Loop: add thread assert() to AddDefer()
Currently fails in class NfsFileReader due to
https://github.com/MusicPlayerDaemon/MPD/issues/1298
2021-10-20 09:26:27 +02:00
Max Kellermann
eb111a10e7 output/pipewire: remove redundant prefix and newline from log message 2021-10-19 14:38:37 +02:00
Max Kellermann
80b09360c6 NEWS: mention the previous commit 2021-10-19 14:38:37 +02:00
Nicolai Syvertsen
5ccf78855d Implement SendTag for PipeWire output plugin 2021-10-19 14:31:40 +02:00
Max Kellermann
fd5a3b5880 client/Response: reimplement Error() without FmtError()
With libfmt versions older than 7, this leads to an endless recursion
between Error() and FmtError(), resulting in a crash due to stack
overflow.  D'oh!

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1295
2021-10-19 13:40:11 +02:00
Max Kellermann
6120c1360c neighbor/Glue: remove unreachable "throw" statement
Should have been removed by commit a8087dc12c
2021-10-19 13:40:11 +02:00
Max Kellermann
a8087dc12c neighbor/Glue: mention failed plugin name in error message 2021-10-19 13:29:00 +02:00
Max Kellermann
070c03dbf7 event/Thread, ...: fix printf->libfmt remains 2021-10-19 13:19:07 +02:00
Max Kellermann
0a9bec3754 increment version number to 0.23.2 2021-10-19 10:29:49 +02:00
Max Kellermann
fff25ac753 release v0.23.1 2021-10-19 10:27:28 +02:00
Max Kellermann
4f1e79b6b8 filter/ReplayGain: emit "mixer" event when replay gain changes volume
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1294
2021-10-19 10:03:21 +02:00
Max Kellermann
aa9933c0b5 output/pipewire: add noexcept 2021-10-19 08:58:50 +02:00
Max Kellermann
0697d1f859 output/pipewire: include cleanup 2021-10-19 08:57:33 +02:00
Max Kellermann
df033fa4aa NEWS: mention the previous commit 2021-10-19 08:56:32 +02:00
Nicolai Syvertsen
b941a7df83 Implement volume updates for pipewire output 2021-10-19 00:01:45 +02:00
Max Kellermann
31151cec3c command/playlist: "load" supports relative positions
This commit also increases the PROTOCOL_VERSION so clients can detect
the availability of the feature.
2021-10-18 22:08:22 +02:00
Max Kellermann
07e8c338df command/queue: move position parameter functions to separate library 2021-10-18 22:07:04 +02:00
Max Kellermann
b22d7218aa command/player, ...: use decimal notation
During the libfmt migration, I converted "%1.3f" to just "{:1.3}"
without the "f" suffix, but libfmt defaults to scientific notation,
which can break some MPD clients.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1291
2021-10-18 16:54:53 +02:00
Max Kellermann
d5be8c74b0 output/pipewire: attempt to change the graph sample rate
Requires PipeWire 0.3.32.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1283
2021-10-18 16:46:23 +02:00
Max Kellermann
c112cb60da output/snapcast: fix typo which caused "Failed to get chunk"
This bug caused a 9 second offset in all time stamps.  Due to that,
the Snapcast server thought the chunks are too old and discarded them.

Fixes https://github.com/MusicPlayerDaemon/MPD/discussions/1287
2021-10-18 16:40:11 +02:00
Max Kellermann
677fa4f9bc doc/plugins.rst: mention that the snapcast output requires a format 2021-10-17 20:01:21 +02:00
Max Kellermann
907af2ad02 Permission: refactor getPermissionFromPassword() to return std::optional
This replaces the output parameter (which is bad API design).  As a
side effect, it fixes the bad [[gnu::pure]] attribute added by commit
a636d2127 which caused optimizing compilers to miscompile calls to
that function.  "Pure" functions can be assumed to have no output
arguments, so the compiler can assume the function doesn't modify
them.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1282
2021-10-17 19:58:50 +02:00
Thomas Zander
6a2e7bbc02 protocol/ArgParser.cxx: Add missing #include <stdio.h>
Fixes a build problem on platforms where stdio.h is not included
transitively. snprintf() is defined in stdio.h.
2021-10-16 17:38:07 +02:00
Max Kellermann
771c46032f meson.build: add missing libfmt dependencies
Fixes https://github.com/MusicPlayerDaemon/MPD/discussions/1281

The problem occurred when there was libfmt-dev installed, but it was
too old (e.g. on Debian Buster), and Meson used the wrap fallback.
Those internal MPD libraries where the libfmt dependency was not
declared were still using the old system libfmt headers, which are not
ABI-compatible with MPD's own libfmt build.
2021-10-15 14:26:59 +02:00
Max Kellermann
85611aa456 storage/smbclient: add StoragePlugin.prefixes
Should have been part of commit
ef24cfa523

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1279
2021-10-15 10:24:30 +02:00
Max Kellermann
466b5cb08d neighbor/smbclient: FmtError() instead of FormatErrno()
Fixes part 2 of https://github.com/MusicPlayerDaemon/MPD/issues/1279
2021-10-15 09:40:36 +02:00
Max Kellermann
3f2f3251cb neighbor/smbclient: use [[gnu::pure]]
Fixes part 1 of https://github.com/MusicPlayerDaemon/MPD/issues/1279
2021-10-15 09:39:34 +02:00
Max Kellermann
8ae85f3991 doc/protocol.rst: move POSITION from "search" to "findadd"
Whoops, I misplaced this one.
2021-10-14 15:36:25 +02:00
Max Kellermann
781fe4ff28 increment version number to 0.23.1 2021-10-14 15:36:16 +02:00
322 changed files with 3362 additions and 1524 deletions

133
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,133 @@
---
on:
workflow_dispatch:
push:
paths-ignore:
- 'android/**'
- 'build/**'
- 'doc/**'
- 'python/**'
- 'subprojects/**'
- 'systemd/**'
- 'win32/**'
branches:
- master
- v0.23.x
pull_request:
paths-ignore:
- 'android/**'
- 'build/**'
- 'doc/**'
- 'python/**'
- 'subprojects/**'
- 'systemd/**'
- 'win32/**'
branches:
- master
- v0.23.x
jobs:
build-linux:
runs-on: ubuntu-latest
env:
CC: 'ccache gcc-10'
CXX: 'ccache g++-10'
steps:
- id: checkout
uses: actions/checkout@v2
- id: cache-ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: linux
- name: Install dependencies
run: |
sudo apt install -y --no-install-recommends \
g++-10 libfmt-dev libboost-dev \
libgtest-dev \
libpcre2-dev \
libsystemd-dev libdbus-1-dev \
libicu-dev \
libcurl4-gnutls-dev \
libpcre2-dev \
libavahi-client-dev \
libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev libogg-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
libmpcdec-dev libwavpack-dev libwildmidi-dev \
libsidplay2-dev libsidutils-dev libresid-builder-dev \
libavcodec-dev libavformat-dev \
libmp3lame-dev libtwolame-dev libshine-dev \
libsamplerate0-dev libsoxr-dev \
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
libzzip-dev \
libyajl-dev libexpat-dev \
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
libpulse-dev libshout3-dev \
libsndio-dev \
libmpdclient-dev \
libnfs-dev \
libupnp-dev \
libsqlite3-dev \
libchromaprint-dev \
libgcrypt20-dev
- name: Full Build
uses: BSFishy/meson-build@v1.0.3
with:
action: test
directory: output/full
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
meson-version: 0.56.0
- name: Mini Build
uses: BSFishy/meson-build@v1.0.3
with:
action: test
directory: output/mini
setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
meson-version: 0.56.0
build-macos:
runs-on: macos-latest
steps:
- id: checkout
uses: actions/checkout@v2
- id: cache-ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: macos
- uses: actions/setup-python@v1
- name: Install dependencies
run: |
brew install \
meson ninja \
fmt \
boost \
googletest \
icu4c \
ffmpeg \
libnfs \
yajl \
libupnp \
libid3tag \
chromaprint \
libsamplerate \
libsoxr \
flac \
opus \
libvorbis \
faad2 \
wavpack \
libmpdclient
- name: Meson Build
uses: BSFishy/meson-build@v1.0.3
with:
action: test
directory: output
setup-options: -Ddocumentation=disabled -Dtest=true
meson-version: 0.56.0

74
NEWS
View File

@@ -1,3 +1,77 @@
ver 0.23.6 (2022/03/14)
* protocol
- support filename "cover.webp" for "albumart" command
- support "readcomments" and "readpicture" on CUE tracks
* decoder
- ffmpeg: fix end-of-file check (update stuck at empty files)
- opus: fix "readpicture" on Opus files
* output
- pipewire: fix crash bug if setting volume before playback starts
- wasapi: fix resume after pause
ver 0.23.5 (2021/12/01)
* protocol
- support relative offsets for "searchadd"
- fix "searchaddpl" bug (bogus error "Bad position")
* database
- upnp: fix crash bug
* tags
- fix MixRamp support
* migrate to PCRE2
* GCC 12 build fixes
ver 0.23.4 (2021/11/11)
* protocol
- add optional position parameter to "searchaddpl"
* decoder
- ffmpeg: support libavcodec 59
* output
- alsa: add option "thesycon_dsd_workaround" to work around device bug
* fix crash on debug builds if startup fails
* systemd
- remove "RuntimeDirectory" directive because it caused problems
- ignore the "pid_file" setting if started as systemd service
* Windows
- enable the "openmpt" decoder plugin
ver 0.23.3 (2021/10/31)
* protocol
- add optional position parameter to "add" and "playlistadd"
- allow range in "playlistdelete"
* database
- fix scanning files with question mark in the name
- inotify: fix use-after-free bug
* output
- alsa: add option "stop_dsd_silence" to work around DSD DAC noise
* macOS: fix libfmt related build failure
* systemd: add "RuntimeDirectory" directive
ver 0.23.2 (2021/10/22)
* protocol
- fix "albumart" timeout bug
* input
- nfs: fix playback bug
* output
- pipewire: send artist and title to PipeWire
- pipewire: DSD support
* neighbor
- mention failed plugin name in error message
* player
- fix cross-fade regression
* fix crash with libfmt versions older than 7
ver 0.23.1 (2021/10/19)
* protocol
- use decimal notation instead of scientific notation
- "load" supports relative positions
* output
- emit "mixer" idle event when replay gain changes volume
- pipewire: emit "mixer" idle events on external volume change
- pipewire: attempt to change the graph sample rate
- snapcast: fix time stamp bug which caused "Failed to get chunk"
* fix libfmt linker problems
* fix broken password authentication
ver 0.23 (2021/10/14) ver 0.23 (2021/10/14)
* protocol * protocol
- new command "getvol" - new command "getvol"

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd" package="org.musicpd"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="60" android:versionCode="65"
android:versionName="0.23"> android:versionName="0.23.5">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
@@ -17,8 +17,6 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application android:allowBackup="true" <application android:allowBackup="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
@@ -43,7 +41,6 @@
<receiver android:name=".Receiver"> <receiver android:name=".Receiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.HEADSET_PLUG" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<service android:name=".Main" android:process=":main"/> <service android:name=".Main" android:process=":main"/>

View File

@@ -13,7 +13,7 @@ android_abi = sys.argv[3]
configure_args = sys.argv[4:] configure_args = sys.argv[4:]
if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')): if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
print("SDK not found in", ndk_path, file=sys.stderr) print("SDK not found in", sdk_path, file=sys.stderr)
sys.exit(1) sys.exit(1)
if not os.path.isdir(ndk_path): if not os.path.isdir(ndk_path):

View File

@@ -13,7 +13,7 @@ GENCLASS="$D/classes"
GENINCLUDE="$D/include" GENINCLUDE="$D/include"
mkdir -p "$GENSRC/$JAVA_PKG_PATH" mkdir -p "$GENSRC/$JAVA_PKG_PATH"
"$JAVAC" -source 1.6 -target 1.6 -Xlint:-options \ "$JAVAC" -source 1.7 -target 1.7 -Xlint:-options \
-cp "$CLASSPATH" \ -cp "$CLASSPATH" \
-h "$GENINCLUDE" \ -h "$GENINCLUDE" \
-d "$GENCLASS" \ -d "$GENCLASS" \

View File

@@ -24,14 +24,13 @@ import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothClass;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
@@ -200,24 +199,14 @@ public class Main extends Service implements Runnable {
return; return;
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_HEADSET_PLUG); filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
registerReceiver(new BroadcastReceiver() { registerReceiver(new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (!mPauseOnHeadphonesDisconnect) { if (!mPauseOnHeadphonesDisconnect)
return; return;
} if (intent.getAction() == AudioManager.ACTION_AUDIO_BECOMING_NOISY)
pause();
if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
if (intent.hasExtra("state") && intent.getIntExtra("state", 0) == 0)
pause();
} else {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBluetoothClass().hasService(BluetoothClass.Service.AUDIO))
pause();
}
} }
}, filter); }, filter);

View File

@@ -25,16 +25,18 @@ import android.content.Intent;
import android.util.Log; import android.util.Log;
public class Receiver extends BroadcastReceiver { public class Receiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.d("Receiver", "onReceive: " + intent); Log.d("Receiver", "onReceive: " + intent);
if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") { if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") {
if (Settings.Preferences.getBoolean(context, if (Settings.Preferences.getBoolean(context,
Settings.Preferences.KEY_RUN_ON_BOOT, false)) { Settings.Preferences.KEY_RUN_ON_BOOT,
final boolean wakelock = Settings.Preferences.getBoolean(context, false)) {
Settings.Preferences.KEY_WAKELOCK, false); final boolean wakelock =
Main.start(context, wakelock); Settings.Preferences.getBoolean(context,
} Settings.Preferences.KEY_WAKELOCK, false);
} Main.start(context, wakelock);
} }
}
}
} }

View File

@@ -38,9 +38,9 @@ author = 'Max Kellermann'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.23' version = '0.23.6'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version + '~git' #release = version + '~git'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@@ -107,6 +107,7 @@ html_theme = 'classic'
# documentation. # documentation.
# #
# html_theme_options = {} # html_theme_options = {}
html_theme_options = {"sidebarwidth": "300px"}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = [] # html_theme_path = []

View File

@@ -128,23 +128,6 @@ audio_output
no audio_output section is specified, then MPD will scan for a usable audio no audio_output section is specified, then MPD will scan for a usable audio
output. output.
replaygain <off or album or track or auto>
If specified, mpd will adjust the volume of songs played using ReplayGain
tags (see https://wiki.hydrogenaud.io/index.php?title=Replaygain).
Setting this to "album" will
adjust volume using the album's ReplayGain tags, while setting it to "track"
will adjust it using the track ReplayGain tags. "auto" uses the track
ReplayGain tags if random play is activated otherwise the album ReplayGain
tags. Currently only FLAC, Ogg Vorbis, Musepack, and MP3 (through ID3v2
ReplayGain tags, not APEv2) are supported.
replaygain_preamp <-15 to 15>
This is the gain (in dB) applied to songs with ReplayGain tags.
volume_normalization <yes or no>
If yes, mpd will normalize the volume of songs as they play. The default is
no.
filesystem_charset <charset> filesystem_charset <charset>
This specifies the character set used for the filesystem. A list of supported This specifies the character set used for the filesystem. A list of supported
character sets can be obtained by running "iconv -l". The default is character sets can be obtained by running "iconv -l". The default is

View File

@@ -26,22 +26,25 @@
# files over an accepted protocol. # files over an accepted protocol.
# #
#db_file "~/.mpd/database" #db_file "~/.mpd/database"
#
# These settings are the locations for the daemon log files for the daemon. # These settings are the locations for the daemon log files for the daemon.
# These logs are great for troubleshooting, depending on your log_level
# settings.
# #
# The special value "syslog" makes MPD use the local syslog daemon. This # The special value "syslog" makes MPD use the local syslog daemon. This
# setting defaults to logging to syslog. # setting defaults to logging to syslog.
# #
#log_file "~/.mpd/log" # If you use systemd, do not configure a log_file. With systemd, MPD
# defaults to the systemd journal, which is fine.
# #
#log_file "~/.mpd/log"
# This setting sets the location of the file which stores the process ID # This setting sets the location of the file which stores the process ID
# for use of mpd --kill and some init scripts. This setting is disabled by # for use of mpd --kill and some init scripts. This setting is disabled by
# default and the pid file will not be stored. # default and the pid file will not be stored.
# #
#pid_file "~/.mpd/pid" # If you use systemd, do not configure a pid_file.
# #
#pid_file "~/.mpd/pid"
# This setting sets the location of the file which contains information about # This setting sets the location of the file which contains information about
# most variables to get MPD back into the same general shape it was in before # most variables to get MPD back into the same general shape it was in before
# it was brought down. This setting is disabled by default and the server # it was brought down. This setting is disabled by default and the server
@@ -76,7 +79,7 @@
# This setting sets the address for the daemon to listen on. Careful attention # This setting sets the address for the daemon to listen on. Careful attention
# should be paid if this is assigned to anything other than the default, any. # should be paid if this is assigned to anything other than the default, any.
# This setting can deny access to control of the daemon. Not effective if # This setting can deny access to control of the daemon. Not effective if
# systemd socket activiation is in use. # systemd socket activation is in use.
# #
# For network # For network
#bind_to_address "any" #bind_to_address "any"
@@ -182,7 +185,7 @@
# cache_directory "~/.local/share/mpd/cache" # cache_directory "~/.local/share/mpd/cache"
#} #}
# #
# An example of database config for a sattelite setup # An example of database config for a satellite setup
# #
#music_directory "nfs://fileserver.local/srv/mp3" #music_directory "nfs://fileserver.local/srv/mp3"
#database { #database {

View File

@@ -61,6 +61,15 @@ upnp
Provides access to UPnP media servers. Provides access to UPnP media servers.
.. list-table::
:widths: 20 80
:header-rows: 1
* - Setting
- Description
* - **interface**
- Interface used to discover media servers. Decided by upnp if left unconfigured.
Storage plugins Storage plugins
=============== ===============
@@ -836,6 +845,16 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
- If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...). - If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...).
* - **dop yes|no** * - **dop yes|no**
- If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk. - If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk.
* - **stop_dsd_silence yes|no**
- If enabled, silence is played before manually stopping playback
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
workaround for some DACs which emit noise when stopping DSD
playback.
* - **thesycon_dsd_workaround yes|no**
- If enabled, enables a workaround for a bug in Thesycon USB
audio receivers. On these devices, playing DSD512 or PCM
causes all subsequent attempts to play other DSD rates to fail,
which can be fixed by briefly playing PCM at 44.1 kHz.
* - **allowed_formats F1 F2 ...** * - **allowed_formats F1 F2 ...**
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list. - Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
@@ -1094,6 +1113,8 @@ Connect to a `PipeWire <https://pipewire.org/>`_ server. Requires
* - **remote NAME** * - **remote NAME**
- The name of the remote to connect to. The default is - The name of the remote to connect to. The default is
``pipewire-0``. ``pipewire-0``.
* - **dsd yes|no**
- Enable DSD playback. This requires PipeWire 0.38.
.. _pulse_plugin: .. _pulse_plugin:
@@ -1193,6 +1214,8 @@ allows MPD to act as a `Snapcast
<https://github.com/badaix/snapcast>`__ server. Snapcast clients <https://github.com/badaix/snapcast>`__ server. Snapcast clients
connect to it and receive audio data from MPD. connect to it and receive audio data from MPD.
You must set a format.
.. list-table:: .. list-table::
:widths: 20 80 :widths: 20 80
:header-rows: 1 :header-rows: 1

View File

@@ -479,7 +479,7 @@ Querying :program:`MPD`'s status
current song in seconds, but with higher resolution. current song in seconds, but with higher resolution.
- ``duration`` [#since_0_20]_: Duration of the current song in seconds. - ``duration`` [#since_0_20]_: Duration of the current song in seconds.
- ``bitrate``: instantaneous bitrate in kbps - ``bitrate``: instantaneous bitrate in kbps
- ``xfade``: ``crossfade`` in seconds - ``xfade``: ``crossfade`` in seconds (see :ref:`crossfading`)
- ``mixrampdb``: ``mixramp`` threshold in dB - ``mixrampdb``: ``mixramp`` threshold in dB
- ``mixrampdelay``: ``mixrampdelay`` in seconds - ``mixrampdelay``: ``mixrampdelay`` in seconds
- ``audio``: The format emitted by the decoder plugin during - ``audio``: The format emitted by the decoder plugin during
@@ -519,17 +519,19 @@ Playback options
.. _command_crossfade: .. _command_crossfade:
:command:`crossfade {SECONDS}` :command:`crossfade {SECONDS}`
Sets crossfading between songs. Sets crossfading between songs. See :ref:`crossfading`.
.. _command_mixrampdb: .. _command_mixrampdb:
:command:`mixrampdb {deciBels}` :command:`mixrampdb {deciBels}`
Sets the threshold at which songs will be overlapped. Like crossfading but doesn't fade the track volume, just overlaps. The songs need to have MixRamp tags added by an external tool. 0dB is the normalized maximum volume so use negative values, I prefer -17dB. In the absence of mixramp tags crossfading will be used. See http://sourceforge.net/projects/mixramp Sets the threshold at which songs will be overlapped.
See :ref:`mixramp`.
.. _command_mixrampdelay: .. _command_mixrampdelay:
:command:`mixrampdelay {SECONDS}` :command:`mixrampdelay {SECONDS}`
Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading. Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
See :ref:`mixramp`.
.. _command_random: .. _command_random:
@@ -551,7 +553,7 @@ Playback options
.. _command_getvol: .. _command_getvol:
:command:`getvol` :command:`getvol` [#since_0_23]_
Read the volume. The result is a ``volume:`` line like in Read the volume. The result is a ``volume:`` line like in
:ref:`status <command_status>`. If there is no mixer, MPD will :ref:`status <command_status>`. If there is no mixer, MPD will
@@ -689,11 +691,14 @@ Whenever possible, ids should be used.
.. _command_add: .. _command_add:
:command:`add {URI}` :command:`add {URI} [POSITION]`
Adds the file ``URI`` to the playlist Adds the file ``URI`` to the playlist
(directories add recursively). ``URI`` (directories add recursively). ``URI``
can also be a single file. can also be a single file.
The position parameter is the same as in :ref:`addid
<command_addid>`. [#since_0_23_3]_
Clients that are connected via local socket may add arbitrary Clients that are connected via local socket may add arbitrary
local files (URI is an absolute path). Example:: local files (URI is an absolute path). Example::
@@ -711,10 +716,10 @@ Whenever possible, ids should be used.
If the second parameter is given, then the song is inserted at the If the second parameter is given, then the song is inserted at the
specified position. If the parameter starts with ``+`` or ``-``, specified position. If the parameter starts with ``+`` or ``-``,
then it is relative to the current song; e.g. ``+0`` inserts right then it is relative to the current song [#since_0_23]_; e.g. ``+0``
after the current song and ``-0`` inserts right before the current inserts right after the current song and ``-0`` inserts right
song (i.e. zero songs between the current song and the newly added before the current song (i.e. zero songs between the current song
song). and the newly added song).
.. _command_clear: .. _command_clear:
@@ -768,8 +773,8 @@ Whenever possible, ids should be used.
.. _command_playlistfind: .. _command_playlistfind:
:command:`playlistfind {FILTER}` :command:`playlistfind {FILTER}`
Finds songs in the queue with strict Search the queue for songs matching
matching. ``FILTER`` (see :ref:`Filters <filter_syntax>`).
.. _command_playlistid: .. _command_playlistid:
@@ -789,8 +794,10 @@ Whenever possible, ids should be used.
.. _command_playlistsearch: .. _command_playlistsearch:
:command:`playlistsearch {FILTER}` :command:`playlistsearch {FILTER}`
Searches case-insensitively for partial matches in the Search the queue for songs matching
queue. ``FILTER`` (see :ref:`Filters <filter_syntax>`).
Parameters have the same meaning as for :ref:`find
<command_playlistfind>`, except that search is not case sensitive.
.. _command_plchanges: .. _command_plchanges:
@@ -923,18 +930,22 @@ remote playlists (absolute URI with a supported scheme).
only a part of the playlist. only a part of the playlist.
The ``POSITION`` parameter specifies where the songs will be The ``POSITION`` parameter specifies where the songs will be
inserted into the queue. (This requires specifying the range as inserted into the queue; it can be relative as described in
well; the special value `0:` can be used if the whole playlist :ref:`addid <command_addid>`. (This requires specifying the range
shall be loaded at a certain queue position.) as well; the special value `0:` can be used if the whole playlist
shall be loaded at a certain queue position.) [#since_0_23_1]_
.. _command_playlistadd: .. _command_playlistadd:
:command:`playlistadd {NAME} {URI}` :command:`playlistadd {NAME} {URI} [POSITION]`
Adds ``URI`` to the playlist Adds ``URI`` to the playlist
`NAME.m3u`. `NAME.m3u`.
`NAME.m3u` will be created if it does `NAME.m3u` will be created if it does
not exist. not exist.
The ``POSITION`` parameter specifies where the songs will be
inserted into the playlist. [#since_0_23_3]_
.. _command_playlistclear: .. _command_playlistclear:
:command:`playlistclear {NAME}` :command:`playlistclear {NAME}`
@@ -946,6 +957,8 @@ remote playlists (absolute URI with a supported scheme).
Deletes ``SONGPOS`` from the Deletes ``SONGPOS`` from the
playlist `NAME.m3u`. playlist `NAME.m3u`.
The second parameter can be a range. [#since_0_23_3]_
.. _command_playlistmove: .. _command_playlistmove:
:command:`playlistmove {NAME} {FROM} {TO}` :command:`playlistmove {NAME} {FROM} {TO}`
@@ -1059,11 +1072,11 @@ The music database
.. _command_findadd: .. _command_findadd:
:command:`findadd {FILTER} [sort {TYPE}] [window {START:END}]` :command:`findadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
Search the database for songs matching Search the database for songs matching
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to ``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
the queue. Parameters have the same meaning as for the queue. Parameters have the same meaning as for
:ref:`find <command_find>`. :ref:`find <command_find>` and :ref:`searchadd <command_searchadd>`.
.. _command_list: .. _command_list:
@@ -1196,15 +1209,12 @@ The music database
.. _command_search: .. _command_search:
:command:`search {FILTER} [sort {TYPE}] [window {START:END}] [position POS]` :command:`search {FILTER} [sort {TYPE}] [window {START:END}]`
Search the database for songs matching Search the database for songs matching
``FILTER`` (see :ref:`Filters <filter_syntax>`). Parameters ``FILTER`` (see :ref:`Filters <filter_syntax>`). Parameters
have the same meaning as for :ref:`find <command_find>`, have the same meaning as for :ref:`find <command_find>`,
except that search is not case sensitive. except that search is not case sensitive.
The ``position`` parameter specifies where the songs will be
inserted.
.. _command_searchadd: .. _command_searchadd:
:command:`searchadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]` :command:`searchadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
@@ -1214,9 +1224,14 @@ The music database
Parameters have the same meaning as for :ref:`search <command_search>`. Parameters have the same meaning as for :ref:`search <command_search>`.
The ``position`` parameter specifies where the songs will be
inserted. [#since_0_23]_
It can be relative to the current song as in :ref:`addid
<command_addid>`. [#since_0_23_5]_
.. _command_searchaddpl: .. _command_searchaddpl:
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}]` :command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
Search the database for songs matching Search the database for songs matching
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to ``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
the playlist named ``NAME``. the playlist named ``NAME``.
@@ -1225,6 +1240,9 @@ The music database
Parameters have the same meaning as for :ref:`search <command_search>`. Parameters have the same meaning as for :ref:`search <command_search>`.
The ``position`` parameter specifies where the songs will be
inserted. [#since_0_23_4]_
.. _command_update: .. _command_update:
:command:`update [URI]` :command:`update [URI]`
@@ -1643,3 +1661,8 @@ client-to-client messages are local to the current partition.
.. [#since_0_20] Since :program:`MPD` 0.20 .. [#since_0_20] Since :program:`MPD` 0.20
.. [#since_0_21] Since :program:`MPD` 0.21 .. [#since_0_21] Since :program:`MPD` 0.21
.. [#since_0_22_4] Since :program:`MPD` 0.22.4 .. [#since_0_22_4] Since :program:`MPD` 0.22.4
.. [#since_0_23] Since :program:`MPD` 0.23
.. [#since_0_23_1] Since :program:`MPD` 0.23.1
.. [#since_0_23_3] Since :program:`MPD` 0.23.3
.. [#since_0_23_4] Since :program:`MPD` 0.23.4
.. [#since_0_23_5] Since :program:`MPD` 0.23.5

View File

@@ -64,13 +64,13 @@ In any case, you need:
Each plugin usually needs a codec library, which you also need to Each plugin usually needs a codec library, which you also need to
install. Check the :doc:`plugins` for details about required libraries install. Check the :doc:`plugins` for details about required libraries
For example, the following installs a fairly complete list of build dependencies on Debian Buster: For example, the following installs a fairly complete list of build dependencies on Debian Bullseye:
.. code-block:: none .. code-block:: none
apt install meson g++ \ apt install meson g++ \
libfmt-dev \ libfmt-dev \
libpcre3-dev \ libpcre2-dev \
libmad0-dev libmpg123-dev libid3tag0-dev \ libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev libogg-dev \ libflac-dev libvorbis-dev libopus-dev libogg-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \ libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
@@ -172,7 +172,9 @@ tarball and change into the directory. Then, instead of
mkdir -p output/win64 mkdir -p output/win64
cd output/win64 cd output/win64
../../win32/build.py --64 ../../win32/build.py --64 \
--buildtype=debugoptimized -Db_ndebug=true \
-Dwrap_mode=forcefallback
This downloads various library sources, and then configures and builds This downloads various library sources, and then configures and builds
:program:`MPD` (for x64; to build a 32 bit binary, pass :program:`MPD` (for x64; to build a 32 bit binary, pass
@@ -182,6 +184,11 @@ around. It is large, but easy to use. If you wish to have a small
mpd.exe with DLLs, you need to compile manually, without the mpd.exe with DLLs, you need to compile manually, without the
:file:`build.py` script. :file:`build.py` script.
The option ``-Dwrap_mode=forcefallback`` tells Meson to download and
cross-compile several libraries used by MPD instead of looking for
them on your computer.
Compiling for Android Compiling for Android
--------------------- ---------------------
@@ -205,8 +212,10 @@ tarball and change into the directory. Then, instead of
mkdir -p output/android mkdir -p output/android
cd output/android cd output/android
../../android/build.py SDK_PATH NDK_PATH ABI ../../android/build.py SDK_PATH NDK_PATH ABI \
meson configure -Dandroid_debug_keystore=$HOME/.android/debug.keystore --buildtype=debugoptimized -Db_ndebug=true \
-Dwrap_mode=forcefallback \
-Dandroid_debug_keystore=$HOME/.android/debug.keystore
ninja android/apk/mpd-debug.apk ninja android/apk/mpd-debug.apk
:envvar:`SDK_PATH` is the absolute path where you installed the :envvar:`SDK_PATH` is the absolute path where you installed the
@@ -456,6 +465,11 @@ The following table lists the audio_output options valid for all plugins:
implement an external mixer, see :ref:`external_mixer`) or no mixer implement an external mixer, see :ref:`external_mixer`) or no mixer
(:samp:`none`). By default, the hardware mixer is used for (:samp:`none`). By default, the hardware mixer is used for
devices which support it, and none for the others. devices which support it, and none for the others.
* - **replay_gain_handler software|mixer|none**
- Specifies how :ref:`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.
* - **filters "name,...**" * - **filters "name,...**"
- The specified configured filters are instantiated in the given - The specified configured filters are instantiated in the given
order. Each filter name refers to a ``filter`` block, see order. Each filter name refers to a ``filter`` block, see
@@ -574,6 +588,85 @@ Sometimes, music needs to be resampled before it can be played; for example, CDs
Check the :ref:`resampler_plugins` reference for a list of resamplers Check the :ref:`resampler_plugins` reference for a list of resamplers
and how to configure them. and how to configure them.
Volume Normalization Settings
-----------------------------
.. _replay_gain:
Replay Gain
^^^^^^^^^^^
The setting ``replaygain`` specifies whether MPD shall adjust the
volume of songs played using `ReplayGain
<https://wiki.hydrogenaud.io/index.php?title=Replaygain>`__ tags.
Setting this to ``album`` will adjust volume using the album's
ReplayGain tags, while setting it to ``track`` will adjust it using
the "track" ReplayGain tags. ``auto`` uses the track ReplayGain tags
if random play is activated otherwise the album ReplayGain
tags.
If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
set to a value (in dB) between ``-15`` and ``15``. This is the gain
applied to songs with ReplayGain tags.
ReplayGain is usually implemented with a software volume filter (which
prevents `Bit-perfect playback`_). To use a hardware mixer, set
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
(see :ref:`config_audio_output` for details).
Simple Volume Normalization
^^^^^^^^^^^^^^^^^^^^^^^^^^^
MPD implements a very simple volume normalization method which can be
enabled by setting ``volume_normalization`` to ``yes``. It supports
16 bit PCM only.
.. _crossfading:
Cross-Fading
------------
If ``crossfade`` is set to a positive number, then adjacent songs are
cross-faded by this number of seconds. This is a run-time setting
:ref:`which can be controlled by clients <command_crossfade>`,
e.g. with :program:`mpc`::
mpc crossfade 10
mpc crossfade 0
Zero means cross-fading is disabled.
Cross-fading is only possible if both songs have the same audio
format. At the cost of quality loss and higher CPU usage, you can
make sure this is always given by configuring
:ref:`audio_output_format`.
.. _mixramp:
MixRamp
^^^^^^^
MixRamp tags describe the loudness levels at start and end of a song
and can be used by MPD to find the best time to begin cross-fading.
MPD enables MixRamp if:
- Cross-fade is enabled
- :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive
value, e.g.::
mpc mixrampdelay 1
- :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value,
e.g.::
mpc mixrampdb -17
- both songs have MixRamp tags
- both songs have the same audio format (or :ref:`audio_output_format`
is configured)
The `MixRamp <http://sourceforge.net/projects/mixramp>`__ tool can be
used to add MixRamp tags to your song files.
Client Connections Client Connections
------------------ ------------------
@@ -1010,6 +1103,15 @@ See :ref:`tags` for a list of supported tags.
The :ref:`metadata_to_use <metadata_to_use>` setting can be used to The :ref:`metadata_to_use <metadata_to_use>` setting can be used to
enable or disable certain tags. enable or disable certain tags.
Note that :program:`MPD` may not necessarily read metadata itself,
instead relying on data reported by the decoder that was used to read
a file. For example, this is the case for the FFmpeg decoder: both
:program:`MPD` and FFmpeg need to support a given metadata format in
order for metadata to be picked up correctly.
Only if a decoder does not have metadata support will :program:`MPD`
attempt to parse a song's metadata itself.
The queue The queue
--------- ---------
@@ -1067,6 +1169,7 @@ Check list for bit-perfect playback:
* Disable sound processing inside ALSA by configuring a "hardware" * Disable sound processing inside ALSA by configuring a "hardware"
device (:samp:`hw:0,0` or similar). device (:samp:`hw:0,0` or similar).
* Don't use software volume (setting :code:`mixer_type`). * Don't use software volume (setting :code:`mixer_type`).
* Don't use :ref:`replay_gain`.
* Don't force :program:`MPD` to use a specific audio format (settings * Don't force :program:`MPD` to use a specific audio format (settings
:code:`format`, :ref:`audio_output_format <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. * 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.

View File

@@ -1,7 +1,7 @@
project( project(
'mpd', 'mpd',
['c', 'cpp'], ['c', 'cpp'],
version: '0.23', version: '0.23.6',
meson_version: '>= 0.56.0', meson_version: '>= 0.56.0',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
@@ -44,7 +44,7 @@ version_conf = configuration_data()
version_conf.set_quoted('PACKAGE', meson.project_name()) version_conf.set_quoted('PACKAGE', meson.project_name())
version_conf.set_quoted('PACKAGE_NAME', meson.project_name()) version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
version_conf.set_quoted('VERSION', meson.project_version()) version_conf.set_quoted('VERSION', meson.project_version())
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.0') version_conf.set_quoted('PROTOCOL_VERSION', '0.23.5')
configure_file(output: 'Version.h', configuration: version_conf) configure_file(output: 'Version.h', configuration: version_conf)
conf = configuration_data() conf = configuration_data()
@@ -265,8 +265,8 @@ sources = [
version_cxx, version_cxx,
'src/Main.cxx', 'src/Main.cxx',
'src/protocol/ArgParser.cxx', 'src/protocol/ArgParser.cxx',
'src/protocol/Result.cxx',
'src/command/CommandError.cxx', 'src/command/CommandError.cxx',
'src/command/PositionArg.cxx',
'src/command/AllCommands.cxx', 'src/command/AllCommands.cxx',
'src/command/QueueCommands.cxx', 'src/command/QueueCommands.cxx',
'src/command/TagCommands.cxx', 'src/command/TagCommands.cxx',

View File

@@ -12,14 +12,14 @@ from build.boost import BoostProject
from build.jack import JackProject from build.jack import JackProject
libmpdclient = MesonProject( libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz', 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.20.tar.xz',
'158aad4c2278ab08e76a3f2b0166c99b39fae00ee17231bd225c5a36e977a189', '18793f68e939c3301e34d8fcadea1f7daa24143941263cecadb80126194e277d',
'lib/libmpdclient.a', 'lib/libmpdclient.a',
) )
libogg = CmakeProject( libogg = CmakeProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz', 'http://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz',
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe', 'c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705',
'lib/libogg.a', 'lib/libogg.a',
[ [
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
@@ -43,8 +43,8 @@ opus = AutotoolsProject(
) )
flac = AutotoolsProject( flac = AutotoolsProject(
'http://downloads.xiph.org/releases/flac/flac-1.3.3.tar.xz', 'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
'213e82bd716c9de6db2f98bcadbc4c24c7e2efe8c75939a1a84e28539c4e1748', '8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
'lib/libFLAC.a', 'lib/libFLAC.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -112,12 +112,16 @@ libmodplug = AutotoolsProject(
) )
libopenmpt = AutotoolsProject( libopenmpt = AutotoolsProject(
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.8+release.autotools.tar.gz', 'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
'61de7cc0c011b10472ca16adcc123689', '892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
'lib/libopenmpt.a', 'lib/libopenmpt.a',
[ [
'--disable-shared', '--enable-static' '--disable-shared', '--enable-static',
'--disable-openmpt123',
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
], ],
base='libopenmpt-0.5.12+release.autotools',
) )
wildmidi = CmakeProject( wildmidi = CmakeProject(
@@ -147,8 +151,8 @@ gme = CmakeProject(
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.4.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz',
'06b10a183ce5371f915c6bb15b7b1fffbe046e8275099c96affc29e17645d909', '51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -376,14 +380,14 @@ ffmpeg = FfmpegProject(
) )
openssl = OpenSSLProject( openssl = OpenSSLProject(
'https://www.openssl.org/source/openssl-3.0.0.tar.gz', 'https://www.openssl.org/source/openssl-3.0.1.tar.gz',
'59eedfcb46c25214c9bd37ed6078297b4df01d012267fe9e9eee31f61bc70536', 'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1',
'include/openssl/ossl_typ.h', 'include/openssl/ossl_typ.h',
) )
curl = CmakeProject( curl = CmakeProject(
'https://curl.se/download/curl-7.79.1.tar.xz', 'https://curl.se/download/curl-7.82.0.tar.xz',
'0606f74b1182ab732a17c11613cbbaf7084f2e6cca432642d0e3ad7c224c3689', '0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'-DBUILD_CURL_EXE=OFF', '-DBUILD_CURL_EXE=OFF',
@@ -411,14 +415,14 @@ curl = CmakeProject(
'-DBUILD_TESTING=OFF', '-DBUILD_TESTING=OFF',
], ],
windows_configure_args=[ windows_configure_args=[
'-DCMAKE_USE_SCHANNEL=ON', '-DCURL_USE_SCHANNEL=ON',
], ],
patches='src/lib/curl/patches', patches='src/lib/curl/patches',
) )
libnfs = AutotoolsProject( libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-4.0.0.tar.gz', 'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
'6ee77e9fe220e2d3e3b1f53cfea04fb319828cc7dbb97dd9df09e46e901d797d', '7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
'lib/libnfs.a', 'lib/libnfs.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -429,8 +433,7 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples', '--disable-utils', '--disable-examples',
], ],
base='libnfs-libnfs-4.0.0', base='libnfs-libnfs-5.0.1',
patches='src/lib/nfs/patches',
autoreconf=True, autoreconf=True,
) )
@@ -441,7 +444,7 @@ jack = JackProject(
) )
boost = BoostProject( boost = BoostProject(
'https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.bz2', 'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2',
'fc9f85fc030e233142908241af7a846e60630aa7388de9a5fafb1f3a26840854', '8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc',
'include/boost/version.hpp', 'include/boost/version.hpp',
) )

View File

@@ -53,19 +53,21 @@ pkgconfig = '{toolchain.pkg_config}'
f.write(f""" f.write(f"""
[properties] [properties]
root = '{toolchain.install_prefix}' root = '{toolchain.install_prefix}'
[built-in options]
c_args = {repr((toolchain.cppflags + ' ' + toolchain.cflags).split())}
c_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
cpp_args = {repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split())}
cpp_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
""") """)
if 'android' in toolchain.arch: if 'android' in toolchain.arch:
f.write(""" f.write("""
# Keep Meson from executing Android-x86 test binariees # Keep Meson from executing Android-x86 test binariees
needs_exe_wrapper = true needs_exe_wrapper = true
""")
f.write(f"""
[built-in options]
c_args = {repr((toolchain.cppflags + ' ' + toolchain.cflags).split())}
c_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
cpp_args = {repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split())}
cpp_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
""") """)
f.write(f""" f.write(f"""

View File

@@ -86,6 +86,9 @@ enum Option {
OPTION_KILL, OPTION_KILL,
OPTION_NO_CONFIG, OPTION_NO_CONFIG,
OPTION_NO_DAEMON, OPTION_NO_DAEMON,
#ifdef __linux__
OPTION_SYSTEMD,
#endif
OPTION_STDOUT, OPTION_STDOUT,
OPTION_STDERR, OPTION_STDERR,
OPTION_VERBOSE, OPTION_VERBOSE,
@@ -98,6 +101,9 @@ static constexpr OptionDef option_defs[] = {
{"kill", "kill the currently running mpd session"}, {"kill", "kill the currently running mpd session"},
{"no-config", "don't read from config"}, {"no-config", "don't read from config"},
{"no-daemon", "don't detach from console"}, {"no-daemon", "don't detach from console"},
#ifdef __linux__
{"systemd", "systemd service mode"},
#endif
{"stdout", nullptr}, // hidden, compatibility with old versions {"stdout", nullptr}, // hidden, compatibility with old versions
{"stderr", "print messages to stderr"}, {"stderr", "print messages to stderr"},
{"verbose", 'v', "verbose logging"}, {"verbose", 'v', "verbose logging"},
@@ -328,7 +334,7 @@ bool ConfigLoader::TryFile(const AllocatedPath &base_path, Path path)
} }
void void
ParseCommandLine(int argc, char **argv, struct options &options, ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
ConfigData &config) ConfigData &config)
{ {
bool use_config_file = true; bool use_config_file = true;
@@ -349,6 +355,13 @@ ParseCommandLine(int argc, char **argv, struct options &options,
options.daemon = false; options.daemon = false;
break; break;
#ifdef __linux__
case OPTION_SYSTEMD:
options.daemon = false;
options.systemd = true;
break;
#endif
case OPTION_STDOUT: case OPTION_STDOUT:
case OPTION_STDERR: case OPTION_STDERR:
options.log_stderr = true; options.log_stderr = true;

View File

@@ -22,15 +22,20 @@
struct ConfigData; struct ConfigData;
struct options { struct CommandLineOptions {
bool kill = false; bool kill = false;
bool daemon = true; bool daemon = true;
#ifdef __linux__
bool systemd = false;
#endif
bool log_stderr = false; bool log_stderr = false;
bool verbose = false; bool verbose = false;
}; };
void void
ParseCommandLine(int argc, char **argv, struct options &options, ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
ConfigData &config); ConfigData &config);
#endif #endif

View File

@@ -32,6 +32,7 @@
#include "net/SocketUtil.hxx" #include "net/SocketUtil.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "fs/StandardDirectory.hxx"
#include "fs/XDG.hxx" #include "fs/XDG.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
@@ -85,13 +86,10 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept
use $XDG_RUNTIME_DIR */ use $XDG_RUNTIME_DIR */
return false; return false;
Path xdg_runtime_dir = Path::FromFS(getenv("XDG_RUNTIME_DIR")); const auto mpd_runtime_dir = GetAppRuntimeDir();
if (xdg_runtime_dir.IsNull()) if (mpd_runtime_dir.IsNull())
return false; return false;
const auto mpd_runtime_dir = xdg_runtime_dir / Path::FromFS("mpd");
mkdir(mpd_runtime_dir.c_str(), 0700);
const auto socket_path = mpd_runtime_dir / Path::FromFS("socket"); const auto socket_path = mpd_runtime_dir / Path::FromFS("socket");
unlink(socket_path.c_str()); unlink(socket_path.c_str());

View File

@@ -48,15 +48,14 @@ LocateFileUri(const char *uri, const Client *client
/* this path was relative to the music /* this path was relative to the music
directory */ directory */
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated) // TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
return LocatedUri(LocatedUri::Type::RELATIVE, return {LocatedUri::Type::RELATIVE, suffix.data()};
suffix.data());
} }
#endif #endif
if (client != nullptr) if (client != nullptr)
client->AllowFile(path); client->AllowFile(path);
return LocatedUri(LocatedUri::Type::PATH, uri, std::move(path)); return {LocatedUri::Type::PATH, uri, std::move(path)};
} }
static LocatedUri static LocatedUri
@@ -90,8 +89,7 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
const auto suffix = storage->MapToRelativeUTF8(uri); const auto suffix = storage->MapToRelativeUTF8(uri);
if (suffix.data() != nullptr) if (suffix.data() != nullptr)
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated) // TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
return LocatedUri(LocatedUri::Type::RELATIVE, return {LocatedUri::Type::RELATIVE, suffix.data()};
suffix.data());
} }
if (kind == UriPluginKind::STORAGE && if (kind == UriPluginKind::STORAGE &&
@@ -99,7 +97,7 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
throw std::invalid_argument("Unsupported URI scheme"); throw std::invalid_argument("Unsupported URI scheme");
#endif #endif
return LocatedUri(LocatedUri::Type::ABSOLUTE, uri); return {LocatedUri::Type::ABSOLUTE, uri};
} }
LocatedUri LocatedUri

View File

@@ -19,6 +19,7 @@
#include "LogBackend.hxx" #include "LogBackend.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "util/Compiler.h"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/StringStrip.hxx" #include "util/StringStrip.hxx"
#include "Version.h" #include "Version.h"
@@ -110,7 +111,7 @@ chomp_length(std::string_view p) noexcept
#ifdef HAVE_SYSLOG #ifdef HAVE_SYSLOG
gcc_const [[gnu::const]]
static int static int
ToSysLogLevel(LogLevel log_level) noexcept ToSysLogLevel(LogLevel log_level) noexcept
{ {

View File

@@ -142,14 +142,24 @@ struct Config {
#ifdef ENABLE_DAEMON #ifdef ENABLE_DAEMON
static void static void
glue_daemonize_init(const struct options *options, glue_daemonize_init(const CommandLineOptions &options,
const ConfigData &config) const ConfigData &config)
{ {
auto pid_file = config.GetPath(ConfigOption::PID_FILE);
#ifdef __linux__
if (options.systemd && pid_file != nullptr) {
pid_file = nullptr;
fprintf(stderr,
"Ignoring the 'pid_file' setting in systemd mode\n");
}
#endif
daemonize_init(config.GetString(ConfigOption::USER), daemonize_init(config.GetString(ConfigOption::USER),
config.GetString(ConfigOption::GROUP), config.GetString(ConfigOption::GROUP),
config.GetPath(ConfigOption::PID_FILE)); std::move(pid_file));
if (options->kill) if (options.kill)
daemonize_kill(); daemonize_kill();
} }
@@ -361,7 +371,8 @@ Instance::BeginShutdownPartitions() noexcept
} }
static inline void static inline void
MainConfigured(const struct options &options, const ConfigData &raw_config) MainConfigured(const CommandLineOptions &options,
const ConfigData &raw_config)
{ {
#ifdef ENABLE_DAEMON #ifdef ENABLE_DAEMON
daemonize_close_stdin(); daemonize_close_stdin();
@@ -384,7 +395,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
const Config config(raw_config); const Config config(raw_config);
#ifdef ENABLE_DAEMON #ifdef ENABLE_DAEMON
glue_daemonize_init(&options, raw_config); glue_daemonize_init(options, raw_config);
#endif #endif
TagLoadConfig(raw_config); TagLoadConfig(raw_config);
@@ -582,7 +593,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
static void static void
AndroidMain() AndroidMain()
{ {
struct options options; CommandLineOptions options;
ConfigData raw_config; ConfigData raw_config;
const auto sdcard = Environment::getExternalStorageDirectory(); const auto sdcard = Environment::getExternalStorageDirectory();
@@ -642,7 +653,7 @@ Java_org_musicpd_Bridge_pause(JNIEnv *, jclass)
static inline void static inline void
MainOrThrow(int argc, char *argv[]) MainOrThrow(int argc, char *argv[])
{ {
struct options options; CommandLineOptions options;
ConfigData raw_config; ConfigData raw_config;
ParseCommandLine(argc, argv, options, raw_config); ParseCommandLine(argc, argv, options, raw_config);

View File

@@ -85,15 +85,15 @@ map_fs_to_utf8(Path path_fs) noexcept
{ {
if (path_fs.IsAbsolute()) { if (path_fs.IsAbsolute()) {
if (global_instance->storage == nullptr) if (global_instance->storage == nullptr)
return std::string(); return {};
const auto music_dir_fs = global_instance->storage->MapFS(""); const auto music_dir_fs = global_instance->storage->MapFS("");
if (music_dir_fs.IsNull()) if (music_dir_fs.IsNull())
return std::string(); return {};
auto relative = music_dir_fs.Relative(path_fs); auto relative = music_dir_fs.Relative(path_fs);
if (relative == nullptr || StringIsEmpty(relative)) if (relative == nullptr || StringIsEmpty(relative))
return std::string(); return {};
path_fs = Path::FromFS(relative); path_fs = Path::FromFS(relative);
} }

View File

@@ -29,8 +29,8 @@ MusicBuffer::MusicBuffer(unsigned num_chunks)
MusicChunkPtr MusicChunkPtr
MusicBuffer::Allocate() noexcept MusicBuffer::Allocate() noexcept
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return MusicChunkPtr(buffer.Allocate(), MusicChunkDeleter(*this)); return {buffer.Allocate(), MusicChunkDeleter(*this)};
} }
void void
@@ -44,7 +44,7 @@ MusicBuffer::Return(MusicChunk *chunk) noexcept
chunk->next.reset(); chunk->next.reset();
chunk->other.reset(); chunk->other.reset();
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
assert(!chunk->other || !chunk->other->other); assert(!chunk->other || !chunk->other->other);

View File

@@ -54,7 +54,7 @@ public:
#endif #endif
bool IsFull() const noexcept { bool IsFull() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return buffer.IsFull(); return buffer.IsFull();
} }

View File

@@ -27,7 +27,7 @@
bool bool
MusicPipe::Contains(const MusicChunk *chunk) const noexcept MusicPipe::Contains(const MusicChunk *chunk) const noexcept
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
for (const MusicChunk *i = head.get(); i != nullptr; i = i->next.get()) for (const MusicChunk *i = head.get(); i != nullptr; i = i->next.get())
if (i == chunk) if (i == chunk)
@@ -41,7 +41,7 @@ MusicPipe::Contains(const MusicChunk *chunk) const noexcept
MusicChunkPtr MusicChunkPtr
MusicPipe::Shift() noexcept MusicPipe::Shift() noexcept
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
auto chunk = std::move(head); auto chunk = std::move(head);
if (chunk != nullptr) { if (chunk != nullptr) {
@@ -81,7 +81,7 @@ MusicPipe::Push(MusicChunkPtr chunk) noexcept
assert(!chunk->IsEmpty()); assert(!chunk->IsEmpty());
assert(chunk->length == 0 || chunk->audio_format.IsValid()); assert(chunk->length == 0 || chunk->audio_format.IsValid());
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
assert(size > 0 || !audio_format.IsDefined()); assert(size > 0 || !audio_format.IsDefined());
assert(!audio_format.IsDefined() || assert(!audio_format.IsDefined() ||

View File

@@ -77,7 +77,7 @@ public:
*/ */
[[gnu::pure]] [[gnu::pure]]
const MusicChunk *Peek() const noexcept { const MusicChunk *Peek() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return head.get(); return head.get();
} }
@@ -101,7 +101,7 @@ public:
*/ */
[[gnu::pure]] [[gnu::pure]]
unsigned GetSize() const noexcept { unsigned GetSize() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return size; return size;
} }

View File

@@ -30,7 +30,6 @@
#include "util/StringView.hxx" #include "util/StringView.hxx"
#include <cassert> #include <cassert>
#include <cstring>
#include <map> #include <map>
#include <string> #include <string>
#include <utility> #include <utility>
@@ -100,18 +99,15 @@ initPermissions(const ConfigData &config)
for (const auto &param : config.GetParamList(ConfigOption::PASSWORD)) { for (const auto &param : config.GetParamList(ConfigOption::PASSWORD)) {
permission_default = 0; permission_default = 0;
param.With([](const char *value){ param.With([](const StringView value){
const char *separator = std::strchr(value, const auto [password, permissions] =
PERMISSION_PASSWORD_CHAR); value.Split(PERMISSION_PASSWORD_CHAR);
if (permissions == nullptr)
if (separator == nullptr)
throw FormatRuntimeError("\"%c\" not found in password string", throw FormatRuntimeError("\"%c\" not found in password string",
PERMISSION_PASSWORD_CHAR); PERMISSION_PASSWORD_CHAR);
std::string password(value, separator); permission_passwords.emplace(password,
parsePermissions(permissions));
unsigned permission = parsePermissions(separator + 1);
permission_passwords.emplace(std::move(password), permission);
}); });
} }
@@ -161,15 +157,14 @@ GetPermissionsFromAddress(SocketAddress address) noexcept
#endif #endif
int std::optional<unsigned>
getPermissionFromPassword(const char *password, unsigned *permission) noexcept GetPermissionFromPassword(const char *password) noexcept
{ {
auto i = permission_passwords.find(password); auto i = permission_passwords.find(password);
if (i == permission_passwords.end()) if (i == permission_passwords.end())
return -1; return std::nullopt;
*permission = i->second; return i->second;
return 0;
} }
unsigned unsigned

View File

@@ -22,6 +22,8 @@
#include "config.h" #include "config.h"
#include <optional>
struct ConfigData; struct ConfigData;
class SocketAddress; class SocketAddress;
@@ -32,9 +34,13 @@ static constexpr unsigned PERMISSION_CONTROL = 4;
static constexpr unsigned PERMISSION_ADMIN = 8; static constexpr unsigned PERMISSION_ADMIN = 8;
static constexpr unsigned PERMISSION_PLAYER = 16; static constexpr unsigned PERMISSION_PLAYER = 16;
/**
* @return the permissions for the given password or std::nullopt if
* the password is not accepted
*/
[[gnu::pure]] [[gnu::pure]]
int std::optional<unsigned>
getPermissionFromPassword(const char *password, unsigned *permission) noexcept; GetPermissionFromPassword(const char *password) noexcept;
[[gnu::const]] [[gnu::const]]
unsigned unsigned

View File

@@ -19,8 +19,8 @@
#include "PlaylistDatabase.hxx" #include "PlaylistDatabase.hxx"
#include "db/PlaylistVector.hxx" #include "db/PlaylistVector.hxx"
#include "fs/io/TextFile.hxx" #include "io/LineReader.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "time/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/StringStrip.hxx" #include "util/StringStrip.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
@@ -42,7 +42,7 @@ playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv)
} }
void void
playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name) playlist_metadata_load(LineReader &file, PlaylistVector &pv, const char *name)
{ {
PlaylistInfo pm(name); PlaylistInfo pm(name);

View File

@@ -24,7 +24,7 @@
class PlaylistVector; class PlaylistVector;
class BufferedOutputStream; class BufferedOutputStream;
class TextFile; class LineReader;
void void
playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv); playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
@@ -33,6 +33,7 @@ playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
* Throws #std::runtime_error on error. * Throws #std::runtime_error on error.
*/ */
void void
playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name); playlist_metadata_load(LineReader &file, PlaylistVector &pv,
const char *name);
#endif #endif

View File

@@ -26,15 +26,15 @@
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "SongLoader.hxx" #include "SongLoader.hxx"
#include "Mapper.hxx" #include "Mapper.hxx"
#include "protocol/RangeArg.hxx"
#include "fs/io/TextFile.hxx" #include "fs/io/TextFile.hxx"
#include "fs/io/FileOutputStream.hxx" #include "io/FileOutputStream.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "config/Data.hxx" #include "config/Data.hxx"
#include "config/Option.hxx" #include "config/Option.hxx"
#include "config/Defaults.hxx" #include "config/Defaults.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "fs/Limits.hxx" #include "fs/Limits.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
#include "fs/FileInfo.hxx" #include "fs/FileInfo.hxx"
@@ -173,11 +173,8 @@ ListPlaylistFiles()
} }
static void static void
SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path) SavePlaylistFile(Path path_fs, const PlaylistFileContents &contents)
{ {
assert(utf8path != nullptr);
const auto path_fs = spl_map_to_fs(utf8path);
assert(!path_fs.IsNull()); assert(!path_fs.IsNull());
FileOutputStream fos(path_fs); FileOutputStream fos(path_fs);
@@ -191,12 +188,11 @@ SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path)
fos.Commit(); fos.Commit();
} }
PlaylistFileContents static PlaylistFileContents
LoadPlaylistFile(const char *utf8path) LoadPlaylistFile(Path path_fs)
try { try {
PlaylistFileContents contents; PlaylistFileContents contents;
const auto path_fs = spl_map_to_fs(utf8path);
assert(!path_fs.IsNull()); assert(!path_fs.IsNull());
TextFile file(path_fs); TextFile file(path_fs);
@@ -251,16 +247,54 @@ try {
throw; throw;
} }
void static PlaylistFileContents
spl_move_index(const char *utf8path, unsigned src, unsigned dest) MaybeLoadPlaylistFile(Path path_fs, PlaylistFileEditor::LoadMode load_mode)
try {
if (load_mode == PlaylistFileEditor::LoadMode::NO)
return {};
return LoadPlaylistFile(path_fs);
} catch (const PlaylistError &error) {
if (error.GetCode() == PlaylistResult::NO_SUCH_LIST &&
load_mode == PlaylistFileEditor::LoadMode::TRY)
return {};
throw;
}
PlaylistFileEditor::PlaylistFileEditor(const char *name_utf8,
LoadMode load_mode)
:path(spl_map_to_fs(name_utf8)),
contents(MaybeLoadPlaylistFile(path, load_mode))
{ {
if (src == dest) }
/* this doesn't check whether the playlist exists, but
what the hell.. */
return;
auto contents = LoadPlaylistFile(utf8path); void
PlaylistFileEditor::Insert(std::size_t i, const char *uri)
{
if (i > size())
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad position");
if (size() >= playlist_max_length)
throw PlaylistError(PlaylistResult::TOO_LARGE,
"Stored playlist is too large");
contents.emplace(std::next(contents.begin(), i), uri);
}
void
PlaylistFileEditor::Insert(std::size_t i, const DetachedSong &song)
{
const char *uri = playlist_saveAbsolutePaths
? song.GetRealURI()
: song.GetURI();
Insert(i, uri);
}
void
PlaylistFileEditor::MoveIndex(unsigned src, unsigned dest)
{
if (src >= contents.size() || dest >= contents.size()) if (src >= contents.size() || dest >= contents.size())
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range"); throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
@@ -270,9 +304,31 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest)
const auto dest_i = std::next(contents.begin(), dest); const auto dest_i = std::next(contents.begin(), dest);
contents.insert(dest_i, std::move(value)); contents.insert(dest_i, std::move(value));
}
SavePlaylistFile(contents, utf8path); void
PlaylistFileEditor::RemoveIndex(unsigned i)
{
if (i >= contents.size())
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
contents.erase(std::next(contents.begin(), i));
}
void
PlaylistFileEditor::RemoveRange(RangeArg range)
{
if (!range.CheckClip(size()))
throw PlaylistError::BadRange();
contents.erase(std::next(contents.begin(), range.start),
std::next(contents.begin(), range.end));
}
void
PlaylistFileEditor::Save()
{
SavePlaylistFile(path, contents);
idle_add(IDLE_STORED_PLAYLIST); idle_add(IDLE_STORED_PLAYLIST);
} }
@@ -314,20 +370,6 @@ spl_delete(const char *name_utf8)
idle_add(IDLE_STORED_PLAYLIST); idle_add(IDLE_STORED_PLAYLIST);
} }
void
spl_remove_index(const char *utf8path, unsigned pos)
{
auto contents = LoadPlaylistFile(utf8path);
if (pos >= contents.size())
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
contents.erase(std::next(contents.begin(), pos));
SavePlaylistFile(contents, utf8path);
idle_add(IDLE_STORED_PLAYLIST);
}
void void
spl_append_song(const char *utf8path, const DetachedSong &song) spl_append_song(const char *utf8path, const DetachedSong &song)
try { try {

View File

@@ -20,19 +20,55 @@
#ifndef MPD_PLAYLIST_FILE_HXX #ifndef MPD_PLAYLIST_FILE_HXX
#define MPD_PLAYLIST_FILE_HXX #define MPD_PLAYLIST_FILE_HXX
#include "fs/AllocatedPath.hxx"
#include <vector> #include <vector>
#include <string> #include <string>
struct ConfigData; struct ConfigData;
struct RangeArg;
class DetachedSong; class DetachedSong;
class SongLoader; class SongLoader;
class PlaylistVector; class PlaylistVector;
class AllocatedPath;
typedef std::vector<std::string> PlaylistFileContents; typedef std::vector<std::string> PlaylistFileContents;
extern bool playlist_saveAbsolutePaths; extern bool playlist_saveAbsolutePaths;
class PlaylistFileEditor {
const AllocatedPath path;
PlaylistFileContents contents;
public:
enum class LoadMode {
NO,
YES,
TRY,
};
/**
* Throws on error.
*/
explicit PlaylistFileEditor(const char *name_utf8, LoadMode load_mode);
auto size() const noexcept {
return contents.size();
}
void Insert(std::size_t i, const char *uri);
void Insert(std::size_t i, const DetachedSong &song);
void MoveIndex(unsigned src, unsigned dest);
void RemoveIndex(unsigned i);
void RemoveRange(RangeArg range);
void Save();
private:
void Load();
};
/** /**
* Perform some global initialization, e.g. load configuration values. * Perform some global initialization, e.g. load configuration values.
*/ */
@@ -55,21 +91,12 @@ spl_map_to_fs(const char *name_utf8);
PlaylistVector PlaylistVector
ListPlaylistFiles(); ListPlaylistFiles();
PlaylistFileContents
LoadPlaylistFile(const char *utf8path);
void
spl_move_index(const char *utf8path, unsigned src, unsigned dest);
void void
spl_clear(const char *utf8path); spl_clear(const char *utf8path);
void void
spl_delete(const char *name_utf8); spl_delete(const char *name_utf8);
void
spl_remove_index(const char *utf8path, unsigned pos);
void void
spl_append_song(const char *utf8path, const DetachedSong &song); spl_append_song(const char *utf8path, const DetachedSong &song);

View File

@@ -28,8 +28,8 @@
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
#include "fs/io/FileOutputStream.hxx" #include "io/FileOutputStream.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "util/UriExtract.hxx" #include "util/UriExtract.hxx"
static void static void

View File

@@ -98,7 +98,7 @@ RemoteTagCache::ItemResolved(Item &item) noexcept
void void
RemoteTagCache::InvokeHandlers() noexcept RemoteTagCache::InvokeHandlers() noexcept
{ {
const std::lock_guard<Mutex> lock(mutex); const std::scoped_lock<Mutex> lock(mutex);
while (!invoke_list.empty()) { while (!invoke_list.empty()) {
auto &item = invoke_list.front(); auto &item = invoke_list.front();
@@ -125,7 +125,7 @@ RemoteTagCache::Item::OnRemoteTag(Tag &&_tag) noexcept
scanner.reset(); scanner.reset();
const std::lock_guard<Mutex> lock(parent.mutex); const std::scoped_lock<Mutex> lock(parent.mutex);
parent.ItemResolved(*this); parent.ItemResolved(*this);
} }
@@ -137,6 +137,6 @@ RemoteTagCache::Item::OnRemoteTagError(std::exception_ptr e) noexcept
scanner.reset(); scanner.reset();
const std::lock_guard<Mutex> lock(parent.mutex); const std::scoped_lock<Mutex> lock(parent.mutex);
parent.ItemResolved(*this); parent.ItemResolved(*this);
} }

View File

@@ -31,7 +31,7 @@ enum class SingleMode : uint8_t {
/** /**
* Return the string representation of a #SingleMode. * Return the string representation of a #SingleMode.
*/ */
[[gnu::pure]] [[gnu::const]]
const char * const char *
SingleToString(SingleMode mode) noexcept; SingleToString(SingleMode mode) noexcept;

View File

@@ -100,7 +100,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
const auto duration = song.GetDuration(); const auto duration = song.GetDuration();
if (!duration.IsNegative()) if (!duration.IsNegative())
r.Fmt(FMT_STRING("Time: {}\n" r.Fmt(FMT_STRING("Time: {}\n"
"duration: {:1.3}\n"), "duration: {:1.3f}\n"),
duration.RoundS(), duration.RoundS(),
duration.ToDoubleS()); duration.ToDoubleS());
} }
@@ -123,7 +123,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
const auto duration = song.GetDuration(); const auto duration = song.GetDuration();
if (!duration.IsNegative()) if (!duration.IsNegative())
r.Fmt(FMT_STRING("Time: {}\n" r.Fmt(FMT_STRING("Time: {}\n"
"duration: {:1.3}\n"), "duration: {:1.3f}\n"),
duration.RoundS(), duration.RoundS(),
duration.ToDoubleS()); duration.ToDoubleS());
} }

View File

@@ -22,8 +22,8 @@
#include "db/plugins/simple/Song.hxx" #include "db/plugins/simple/Song.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "TagSave.hxx" #include "TagSave.hxx"
#include "fs/io/TextFile.hxx" #include "io/LineReader.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "tag/ParseName.hxx" #include "tag/ParseName.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "tag/Builder.hxx" #include "tag/Builder.hxx"
@@ -85,7 +85,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
} }
DetachedSong DetachedSong
song_load(TextFile &file, const char *uri, song_load(LineReader &file, const char *uri,
std::string *target_r) std::string *target_r)
{ {
DetachedSong song(uri); DetachedSong song(uri);

View File

@@ -28,7 +28,7 @@ struct Song;
struct AudioFormat; struct AudioFormat;
class DetachedSong; class DetachedSong;
class BufferedOutputStream; class BufferedOutputStream;
class TextFile; class LineReader;
void void
song_save(BufferedOutputStream &os, const Song &song); song_save(BufferedOutputStream &os, const Song &song);
@@ -43,7 +43,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
* Throws on error. * Throws on error.
*/ */
DetachedSong DetachedSong
song_load(TextFile &file, const char *uri, song_load(LineReader &file, const char *uri,
std::string *target_r=nullptr); std::string *target_r=nullptr);
#endif #endif

View File

@@ -22,8 +22,8 @@
#include "output/State.hxx" #include "output/State.hxx"
#include "queue/PlaylistState.hxx" #include "queue/PlaylistState.hxx"
#include "fs/io/TextFile.hxx" #include "fs/io/TextFile.hxx"
#include "fs/io/FileOutputStream.hxx" #include "io/FileOutputStream.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "storage/StorageState.hxx" #include "storage/StorageState.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"

View File

@@ -21,12 +21,16 @@
#include "TagStream.hxx" #include "TagStream.hxx"
#include "TagFile.hxx" #include "TagFile.hxx"
#include "tag/Generic.hxx" #include "tag/Generic.hxx"
#include "song/LightSong.hxx"
#include "db/Interface.hxx"
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "protocol/Ack.hxx" #include "protocol/Ack.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "util/Compiler.h" #include "util/Compiler.h"
#include "util/ScopeExit.hxx"
#include "util/StringCompare.hxx"
#include "util/UriExtract.hxx" #include "util/UriExtract.hxx"
#include "LocateUri.hxx" #include "LocateUri.hxx"
@@ -51,10 +55,67 @@ TagScanFile(const Path path_fs, TagHandler &handler)
ScanGenericTags(path_fs, handler); ScanGenericTags(path_fs, handler);
} }
#ifdef ENABLE_DATABASE
/**
* Collapse "../" prefixes in a URI relative to the specified base
* URI.
*/
static std::string
ResolveUri(std::string_view base, const char *relative)
{
while (true) {
const char *rest = StringAfterPrefix(relative, "../");
if (rest == nullptr)
break;
if (base == ".")
throw ProtocolError(ACK_ERROR_NO_EXIST, "Bad real URI");
base = PathTraitsUTF8::GetParent(base);
relative = rest;
}
return PathTraitsUTF8::Build(base, relative);
}
/**
* Look up the specified song in the database and return its
* (resolved) "real" URI.
*/
static std::string
GetRealSongUri(Client &client, std::string_view uri)
{
const auto &db = client.GetDatabaseOrThrow();
const auto *song = db.GetSong(uri);
if (song == nullptr)
throw ProtocolError(ACK_ERROR_NO_EXIST, "No such song");
AtScopeExit(&db, song) { db.ReturnSong(song); };
if (song->real_uri == nullptr)
return {};
return ResolveUri(PathTraitsUTF8::GetParent(uri), song->real_uri);
}
#endif
static void static void
TagScanDatabase(Client &client, const char *uri, TagHandler &handler) TagScanDatabase(Client &client, const char *uri, TagHandler &handler)
{ {
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
const auto real_uri = GetRealSongUri(client, uri);
if (!real_uri.empty()) {
uri = real_uri.c_str();
// TODO: support absolute paths?
if (uri_has_scheme(uri))
return TagScanStream(uri, handler);
}
const Storage *storage = client.GetStorage(); const Storage *storage = client.GetStorage();
if (storage == nullptr) { if (storage == nullptr) {
#else #else

View File

@@ -60,7 +60,7 @@ tag_print(Response &r, const Tag &tag) noexcept
{ {
if (!tag.duration.IsNegative()) if (!tag.duration.IsNegative())
r.Fmt(FMT_STRING("Time: {}\n" r.Fmt(FMT_STRING("Time: {}\n"
"duration: {:1.3}\n"), "duration: {:1.3f}\n"),
tag.duration.RoundS(), tag.duration.RoundS(),
tag.duration.ToDoubleS()); tag.duration.ToDoubleS());

View File

@@ -19,7 +19,7 @@
#include "TagSave.hxx" #include "TagSave.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#define SONG_TIME "Time: " #define SONG_TIME "Time: "

View File

@@ -26,7 +26,6 @@
#include "plugins/ZzipArchivePlugin.hxx" #include "plugins/ZzipArchivePlugin.hxx"
#include <cassert> #include <cassert>
#include <iterator>
#include <string.h> #include <string.h>

View File

@@ -162,7 +162,7 @@ class Iso9660InputStream final : public InputStream {
std::array<uint8_t, ISO_BLOCKSIZE> data; std::array<uint8_t, ISO_BLOCKSIZE> data;
public: public:
ConstBuffer<uint8_t> Read() const noexcept { [[nodiscard]] ConstBuffer<uint8_t> Read() const noexcept {
assert(fill <= data.size()); assert(fill <= data.size());
assert(position <= fill); assert(position <= fill);

View File

@@ -35,7 +35,7 @@
#include <utility> #include <utility>
#include <inttypes.h> /* for PRIoffset (PRIu64) */ #include <cinttypes> /* for PRIoffset (PRIu64) */
struct ZzipDir { struct ZzipDir {
ZZIP_DIR *const dir; ZZIP_DIR *const dir;

View File

@@ -150,7 +150,13 @@ public:
/** /**
* Write a null-terminated string. * Write a null-terminated string.
*/ */
bool Write(const char *data) noexcept; bool Write(std::string_view s) noexcept {
return Write(s.data(), s.size());
}
bool WriteOK() noexcept {
return Write("OK\n");
}
/** /**
* returns the uid of the client process, or a negative value * returns the uid of the client process, or a negative value

View File

@@ -20,7 +20,6 @@
#include "Client.hxx" #include "Client.hxx"
#include "Config.hxx" #include "Config.hxx"
#include "Domain.hxx" #include "Domain.hxx"
#include "protocol/Result.hxx"
#include "command/AllCommands.hxx" #include "command/AllCommands.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
@@ -72,7 +71,7 @@ Client::ProcessLine(char *line) noexcept
if (idle_waiting) { if (idle_waiting) {
/* send empty idle response and leave idle mode */ /* send empty idle response and leave idle mode */
idle_waiting = false; idle_waiting = false;
command_success(*this); WriteOK();
} }
/* do nothing if the client wasn't idling: the client /* do nothing if the client wasn't idling: the client
@@ -108,7 +107,7 @@ Client::ProcessLine(char *line) noexcept
"list returned {}", id, unsigned(ret)); "list returned {}", id, unsigned(ret));
if (ret == CommandResult::OK) if (ret == CommandResult::OK)
command_success(*this); WriteOK();
return ret; return ret;
} else { } else {
@@ -144,7 +143,7 @@ Client::ProcessLine(char *line) noexcept
return CommandResult::CLOSE; return CommandResult::CLOSE;
if (ret == CommandResult::OK) if (ret == CommandResult::OK)
command_success(*this); WriteOK();
return ret; return ret;
} }

View File

@@ -66,7 +66,11 @@ Response::WriteBinary(ConstBuffer<void> payload) noexcept
void void
Response::Error(enum ack code, const char *msg) noexcept Response::Error(enum ack code, const char *msg) noexcept
{ {
FmtError(code, FMT_STRING("{}"), msg); Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
(int)code, list_index, command);
Write(msg);
Write("\n");
} }
void void
@@ -76,7 +80,7 @@ Response::VFmtError(enum ack code,
Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "), Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
(int)code, list_index, command); (int)code, list_index, command);
VFmt(format_str, std::move(args)); VFmt(format_str, args);
Write("\n"); Write("\n");
} }

View File

@@ -21,7 +21,6 @@
#include "Client.hxx" #include "Client.hxx"
#include "Response.hxx" #include "Response.hxx"
#include "command/CommandError.hxx" #include "command/CommandError.hxx"
#include "protocol/Result.hxx"
ThreadBackgroundCommand::ThreadBackgroundCommand(Client &_client) noexcept ThreadBackgroundCommand::ThreadBackgroundCommand(Client &_client) noexcept
:thread(BIND_THIS_METHOD(_Run)), :thread(BIND_THIS_METHOD(_Run)),
@@ -57,7 +56,7 @@ ThreadBackgroundCommand::DeferredFinish() noexcept
PrintError(response, error); PrintError(response, error);
} else { } else {
SendResponse(response); SendResponse(response);
command_success(client); client.WriteOK();
} }
/* delete this object */ /* delete this object */

View File

@@ -27,9 +27,3 @@ Client::Write(const void *data, size_t length) noexcept
/* if the client is going to be closed, do nothing */ /* if the client is going to be closed, do nothing */
return !IsExpired() && FullyBufferedSocket::Write(data, length); return !IsExpired() && FullyBufferedSocket::Write(data, length);
} }
bool
Client::Write(const char *data) noexcept
{
return Write(data, strlen(data));
}

View File

@@ -85,7 +85,7 @@ handle_not_commands(Client &client, Request request, Response &response);
* This array must be sorted! * This array must be sorted!
*/ */
static constexpr struct command commands[] = { static constexpr struct command commands[] = {
{ "add", PERMISSION_ADD, 1, 1, handle_add }, { "add", PERMISSION_ADD, 1, 2, handle_add },
{ "addid", PERMISSION_ADD, 1, 2, handle_addid }, { "addid", PERMISSION_ADD, 1, 2, handle_addid },
{ "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid }, { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
{ "albumart", PERMISSION_READ, 2, 2, handle_album_art }, { "albumart", PERMISSION_READ, 2, 2, handle_album_art },
@@ -157,7 +157,7 @@ static constexpr struct command commands[] = {
{ "play", PERMISSION_PLAYER, 0, 1, handle_play }, { "play", PERMISSION_PLAYER, 0, 1, handle_play },
{ "playid", PERMISSION_PLAYER, 0, 1, handle_playid }, { "playid", PERMISSION_PLAYER, 0, 1, handle_playid },
{ "playlist", PERMISSION_READ, 0, 0, handle_playlist }, { "playlist", PERMISSION_READ, 0, 0, handle_playlist },
{ "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, { "playlistadd", PERMISSION_CONTROL, 2, 3, handle_playlistadd },
{ "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
{ "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete },
{ "playlistfind", PERMISSION_READ, 1, -1, handle_playlistfind }, { "playlistfind", PERMISSION_READ, 1, -1, handle_playlistfind },

View File

@@ -58,13 +58,13 @@ handle_binary_limit(Client &client, Request args,
CommandResult CommandResult
handle_password(Client &client, Request args, Response &r) handle_password(Client &client, Request args, Response &r)
{ {
unsigned permission = 0; const auto permission = GetPermissionFromPassword(args.front());
if (getPermissionFromPassword(args.front(), &permission) < 0) { if (!permission) {
r.Error(ACK_ERROR_PASSWORD, "incorrect password"); r.Error(ACK_ERROR_PASSWORD, "incorrect password");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
client.SetPermission(permission); client.SetPermission(*permission);
return CommandResult::OK; return CommandResult::OK;
} }

View File

@@ -18,6 +18,7 @@
*/ */
#include "DatabaseCommands.hxx" #include "DatabaseCommands.hxx"
#include "PositionArg.hxx"
#include "Request.hxx" #include "Request.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "db/DatabaseQueue.hxx" #include "db/DatabaseQueue.hxx"
@@ -86,6 +87,20 @@ ParseQueuePosition(Request &args, unsigned queue_length)
return queue_length; return queue_length;
} }
static unsigned
ParseInsertPosition(Request &args, const playlist &playlist)
{
if (args.size >= 2 && StringIsEqual(args[args.size - 2], "position")) {
unsigned position = ParseInsertPosition(args.back(), playlist);
args.pop_back();
args.pop_back();
return position;
}
/* append to the end of the queue by default */
return playlist.queue.GetLength();
}
/** /**
* Convert all remaining arguments to a #DatabaseSelection. * Convert all remaining arguments to a #DatabaseSelection.
* *
@@ -160,7 +175,8 @@ handle_match_add(Client &client, Request args, bool fold_case)
{ {
auto &partition = client.GetPartition(); auto &partition = client.GetPartition();
const auto queue_length = partition.playlist.queue.GetLength(); const auto queue_length = partition.playlist.queue.GetLength();
const unsigned position = ParseQueuePosition(args, queue_length); const unsigned position =
ParseInsertPosition(args, partition.playlist);
SongFilter filter; SongFilter filter;
const auto selection = ParseDatabaseSelection(args, fold_case, filter); const auto selection = ParseDatabaseSelection(args, fold_case, filter);
@@ -199,13 +215,20 @@ handle_searchaddpl(Client &client, Request args, Response &)
{ {
const char *playlist = args.shift(); const char *playlist = args.shift();
const unsigned position = ParseQueuePosition(args, UINT_MAX);
SongFilter filter; SongFilter filter;
const auto selection = ParseDatabaseSelection(args, true, filter); const auto selection = ParseDatabaseSelection(args, true, filter);
const Database &db = client.GetDatabaseOrThrow(); const Database &db = client.GetDatabaseOrThrow();
search_add_to_playlist(db, client.GetStorage(), if (position == UINT_MAX)
playlist, selection); search_add_to_playlist(db, client.GetStorage(),
playlist, selection);
else
SearchInsertIntoPlaylist(db, client.GetStorage(), selection,
playlist, position);
return CommandResult::OK; return CommandResult::OK;
} }

View File

@@ -160,8 +160,7 @@ find_stream_art(std::string_view directory, Mutex &mutex)
static constexpr auto art_names = std::array { static constexpr auto art_names = std::array {
"cover.png", "cover.png",
"cover.jpg", "cover.jpg",
"cover.tiff", "cover.webp",
"cover.bmp",
}; };
for(const auto name : art_names) { for(const auto name : art_names) {
@@ -213,7 +212,7 @@ read_stream_art(Response &r, const std::string_view art_directory,
std::min<offset_type>(art_file_size - offset, std::min<offset_type>(art_file_size - offset,
r.GetClient().binary_limit); r.GetClient().binary_limit);
std::unique_ptr<std::byte[]> buffer(new std::byte[buffer_size]); auto buffer = std::make_unique<std::byte[]>(buffer_size);
std::size_t read_size = 0; std::size_t read_size = 0;
if (buffer_size > 0) { if (buffer_size > 0) {

View File

@@ -67,7 +67,7 @@ protected:
} }
void CancelThread() noexcept override { void CancelThread() noexcept override {
const std::lock_guard<Mutex> lock(mutex); const std::scoped_lock<Mutex> lock(mutex);
cancel = true; cancel = true;
cond.notify_one(); cond.notify_one();
} }
@@ -204,7 +204,7 @@ GetChromaprintCommand::DecodeFile(std::string_view suffix, InputStream &is,
return false; return false;
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
if (cancel) if (cancel)
throw StopDecoder(); throw StopDecoder();
} }
@@ -224,10 +224,12 @@ GetChromaprintCommand::DecodeFile(std::string_view suffix, InputStream &is,
inline void inline void
GetChromaprintCommand::DecodeFile() GetChromaprintCommand::DecodeFile()
{ {
const auto suffix = uri_get_suffix(uri); const char *_suffix = PathTraitsUTF8::GetFilenameSuffix(uri.c_str());
if (suffix.empty()) if (_suffix == nullptr)
return; return;
const std::string_view suffix{_suffix};
InputStreamPtr input_stream; InputStreamPtr input_stream;
try { try {

View File

@@ -186,7 +186,8 @@ handle_moveoutput(Client &client, Request request, Response &response)
was_enabled); was_enabled);
else else
/* copy the AudioOutputControl and add it to the output list */ /* copy the AudioOutputControl and add it to the output list */
dest_partition.outputs.AddCopy(output,was_enabled); dest_partition.outputs.AddMoveFrom(std::move(*output),
was_enabled);
instance.EmitIdle(IDLE_OUTPUT); instance.EmitIdle(IDLE_OUTPUT);
return CommandResult::OK; return CommandResult::OK;

View File

@@ -171,7 +171,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
if (player_status.state != PlayerState::STOP) { if (player_status.state != PlayerState::STOP) {
r.Fmt(FMT_STRING(COMMAND_STATUS_TIME ": {}:{}\n" r.Fmt(FMT_STRING(COMMAND_STATUS_TIME ": {}:{}\n"
"elapsed: {:1.3}\n" "elapsed: {:1.3f}\n"
COMMAND_STATUS_BITRATE ": {}\n"), COMMAND_STATUS_BITRATE ": {}\n"),
player_status.elapsed_time.RoundS(), player_status.elapsed_time.RoundS(),
player_status.total_time.IsNegative() player_status.total_time.IsNegative()
@@ -181,7 +181,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
player_status.bit_rate); player_status.bit_rate);
if (!player_status.total_time.IsNegative()) if (!player_status.total_time.IsNegative())
r.Fmt(FMT_STRING("duration: {:1.3}\n"), r.Fmt(FMT_STRING("duration: {:1.3f}\n"),
player_status.total_time.ToDoubleS()); player_status.total_time.ToDoubleS());
if (player_status.audio_format.IsDefined()) if (player_status.audio_format.IsDefined())

View File

@@ -19,10 +19,13 @@
#include "config.h" #include "config.h"
#include "PlaylistCommands.hxx" #include "PlaylistCommands.hxx"
#include "PositionArg.hxx"
#include "Request.hxx" #include "Request.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "db/Interface.hxx"
#include "db/Selection.hxx" #include "db/Selection.hxx"
#include "db/DatabasePlaylist.hxx" #include "db/DatabasePlaylist.hxx"
#include "db/DatabaseSong.hxx"
#include "PlaylistSave.hxx" #include "PlaylistSave.hxx"
#include "PlaylistFile.hxx" #include "PlaylistFile.hxx"
#include "PlaylistError.hxx" #include "PlaylistError.hxx"
@@ -86,7 +89,7 @@ handle_load(Client &client, Request args, [[maybe_unused]] Response &r)
const unsigned old_size = playlist.GetLength(); const unsigned old_size = playlist.GetLength();
const unsigned position = args.size > 2 const unsigned position = args.size > 2
? args.ParseUnsigned(2, old_size) ? ParseInsertPosition(args[2], partition.playlist)
: old_size; : old_size;
const SongLoader loader(client); const SongLoader loader(client);
@@ -172,9 +175,11 @@ handle_playlistdelete([[maybe_unused]] Client &client,
Request args, [[maybe_unused]] Response &r) Request args, [[maybe_unused]] Response &r)
{ {
const char *const name = args[0]; const char *const name = args[0];
unsigned from = args.ParseUnsigned(1); const auto range = args.ParseRange(1);
spl_remove_index(name, from); PlaylistFileEditor editor(name, PlaylistFileEditor::LoadMode::YES);
editor.RemoveRange(range);
editor.Save();
return CommandResult::OK; return CommandResult::OK;
} }
@@ -186,7 +191,14 @@ handle_playlistmove([[maybe_unused]] Client &client,
unsigned from = args.ParseUnsigned(1); unsigned from = args.ParseUnsigned(1);
unsigned to = args.ParseUnsigned(2); unsigned to = args.ParseUnsigned(2);
spl_move_index(name, from, to); if (from == to)
/* this doesn't check whether the playlist exists, but
what the hell.. */
return CommandResult::OK;
PlaylistFileEditor editor(name, PlaylistFileEditor::LoadMode::YES);
editor.MoveIndex(from, to);
editor.Save();
return CommandResult::OK; return CommandResult::OK;
} }
@@ -200,12 +212,55 @@ handle_playlistclear([[maybe_unused]] Client &client,
return CommandResult::OK; return CommandResult::OK;
} }
static CommandResult
handle_playlistadd_position(Client &client, const char *playlist_name,
const char *uri, unsigned position,
Response &r)
{
PlaylistFileEditor editor{
playlist_name,
PlaylistFileEditor::LoadMode::TRY,
};
if (position > editor.size()) {
r.Error(ACK_ERROR_ARG, "Bad position");
return CommandResult::ERROR;
}
if (uri_has_scheme(uri)) {
editor.Insert(position, uri);
} else {
#ifdef ENABLE_DATABASE
const DatabaseSelection selection(uri, true, nullptr);
if (SearchInsertIntoPlaylist(client.GetDatabaseOrThrow(),
client.GetStorage(),
selection,
editor, position) == 0)
/* no song was found, don't need to save */
return CommandResult::OK;
#else
(void)client;
r.Error(ACK_ERROR_NO_EXIST, "No database");
return CommandResult::ERROR;
#endif
}
editor.Save();
return CommandResult::OK;
}
CommandResult CommandResult
handle_playlistadd(Client &client, Request args, [[maybe_unused]] Response &r) handle_playlistadd(Client &client, Request args, [[maybe_unused]] Response &r)
{ {
const char *const playlist = args[0]; const char *const playlist = args[0];
const char *const uri = args[1]; const char *const uri = args[1];
if (args.size >= 3)
return handle_playlistadd_position(client, playlist, uri,
args.ParseUnsigned(2), r);
if (uri_has_scheme(uri)) { if (uri_has_scheme(uri)) {
const SongLoader loader(client); const SongLoader loader(client);
spl_append_uri(playlist, loader, uri); spl_append_uri(playlist, loader, uri);

103
src/command/PositionArg.cxx Normal file
View File

@@ -0,0 +1,103 @@
/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "PositionArg.hxx"
#include "protocol/Ack.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/RangeArg.hxx"
#include "queue/Playlist.hxx"
static unsigned
RequireCurrentPosition(const playlist &p)
{
int position = p.GetCurrentPosition();
if (position < 0)
throw ProtocolError(ACK_ERROR_PLAYER_SYNC,
"No current song");
return position;
}
unsigned
ParseInsertPosition(const char *s, const playlist &playlist)
{
const auto queue_length = playlist.queue.GetLength();
if (*s == '+') {
/* after the current song */
const unsigned current = RequireCurrentPosition(playlist);
assert(current < queue_length);
return current + 1 +
ParseCommandArgUnsigned(s + 1,
queue_length - current - 1);
} else if (*s == '-') {
/* before the current song */
const unsigned current = RequireCurrentPosition(playlist);
assert(current < queue_length);
return current - ParseCommandArgUnsigned(s + 1, current);
} else
/* absolute position */
return ParseCommandArgUnsigned(s, queue_length);
}
unsigned
ParseMoveDestination(const char *s, const RangeArg range, const playlist &p)
{
assert(!range.IsEmpty());
assert(!range.IsOpenEnded());
const unsigned queue_length = p.queue.GetLength();
if (*s == '+') {
/* after the current song */
unsigned current = RequireCurrentPosition(p);
assert(current < queue_length);
if (range.Contains(current))
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
if (current >= range.end)
current -= range.Count();
return current + 1 +
ParseCommandArgUnsigned(s + 1,
queue_length - current - range.Count());
} else if (*s == '-') {
/* before the current song */
unsigned current = RequireCurrentPosition(p);
assert(current < queue_length);
if (range.Contains(current))
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
if (current >= range.end)
current -= range.Count();
return current -
ParseCommandArgUnsigned(s + 1,
queue_length - current - range.Count());
} else
/* absolute position */
return ParseCommandArgUnsigned(s,
queue_length - range.Count());
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2003-2021 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.
*/
#pragma once
struct playlist;
struct RangeArg;
/**
* Throws #ProtocolError on error.
*/
unsigned
ParseInsertPosition(const char *s, const playlist &playlist);
unsigned
ParseMoveDestination(const char *s, const RangeArg range, const playlist &p);

View File

@@ -19,6 +19,7 @@
#include "config.h" #include "config.h"
#include "QueueCommands.hxx" #include "QueueCommands.hxx"
#include "PositionArg.hxx"
#include "Request.hxx" #include "Request.hxx"
#include "protocol/RangeArg.hxx" #include "protocol/RangeArg.hxx"
#include "db/DatabaseQueue.hxx" #include "db/DatabaseQueue.hxx"
@@ -43,17 +44,6 @@
#include <limits> #include <limits>
static unsigned
RequireCurrentPosition(const playlist &p)
{
int position = p.GetCurrentPosition();
if (position < 0)
throw ProtocolError(ACK_ERROR_PLAYER_SYNC,
"No current song");
return position;
}
static void static void
AddUri(Client &client, const LocatedUri &uri) AddUri(Client &client, const LocatedUri &uri)
{ {
@@ -62,29 +52,24 @@ AddUri(Client &client, const LocatedUri &uri)
SongLoader(client).LoadSong(uri)); SongLoader(client).LoadSong(uri));
} }
static CommandResult
AddDatabaseSelection(Client &client, const char *uri,
[[maybe_unused]] Response &r)
{
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
auto &partition = client.GetPartition();
static void
AddDatabaseSelection(Partition &partition, const char *uri)
{
const ScopeBulkEdit bulk_edit(partition); const ScopeBulkEdit bulk_edit(partition);
const DatabaseSelection selection(uri, true); const DatabaseSelection selection(uri, true);
AddFromDatabase(partition, selection); AddFromDatabase(partition, selection);
return CommandResult::OK;
#else
(void)client;
(void)uri;
r.Error(ACK_ERROR_NO_EXIST, "No database");
return CommandResult::ERROR;
#endif
} }
#endif
CommandResult CommandResult
handle_add(Client &client, Request args, Response &r) handle_add(Client &client, Request args, [[maybe_unused]] Response &r)
{ {
auto &partition = client.GetPartition();
const char *uri = args.front(); const char *uri = args.front();
if (StringIsEqual(uri, "/")) if (StringIsEqual(uri, "/"))
/* this URI is malformed, but some clients are buggy /* this URI is malformed, but some clients are buggy
@@ -94,6 +79,11 @@ handle_add(Client &client, Request args, Response &r)
here */ here */
uri = ""; uri = "";
const auto old_size = partition.playlist.GetLength();
const unsigned position = args.size > 1
? ParseInsertPosition(args[1], partition.playlist)
: old_size;
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
&client &client
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
@@ -104,18 +94,34 @@ handle_add(Client &client, Request args, Response &r)
case LocatedUri::Type::ABSOLUTE: case LocatedUri::Type::ABSOLUTE:
AddUri(client, located_uri); AddUri(client, located_uri);
client.GetInstance().LookupRemoteTag(located_uri.canonical_uri); client.GetInstance().LookupRemoteTag(located_uri.canonical_uri);
return CommandResult::OK; break;
case LocatedUri::Type::PATH: case LocatedUri::Type::PATH:
AddUri(client, located_uri); AddUri(client, located_uri);
return CommandResult::OK; break;
case LocatedUri::Type::RELATIVE: case LocatedUri::Type::RELATIVE:
return AddDatabaseSelection(client, located_uri.canonical_uri, #ifdef ENABLE_DATABASE
r); AddDatabaseSelection(partition, located_uri.canonical_uri);
break;
#else
r.Error(ACK_ERROR_NO_EXIST, "No database");
return CommandResult::ERROR;
#endif
} }
gcc_unreachable(); if (position < old_size) {
const unsigned new_size = partition.playlist.GetLength();
const RangeArg move_range{old_size, new_size};
try {
partition.MoveRange(move_range, position);
} catch (...) {
/* ignore - shall we handle it? */
}
}
return CommandResult::OK;
} }
CommandResult CommandResult
@@ -129,30 +135,8 @@ handle_addid(Client &client, Request args, Response &r)
const auto queue_length = partition.playlist.queue.GetLength(); const auto queue_length = partition.playlist.queue.GetLength();
if (args.size > 1) { if (args.size > 1)
const char *const s = args[1]; to = ParseInsertPosition(args[1], partition.playlist);
if (*s == '+') {
/* after the current song */
const unsigned current =
RequireCurrentPosition(partition.playlist);
assert(current < queue_length);
to = current + 1 +
ParseCommandArgUnsigned(s + 1,
queue_length - current - 1);
} else if (*s == '-') {
/* before the current song */
const unsigned current =
RequireCurrentPosition(partition.playlist);
assert(current < queue_length);
to = current - ParseCommandArgUnsigned(s + 1, current);
} else
/* absolute position */
to = args.ParseUnsigned(1, queue_length);
}
const SongLoader loader(client); const SongLoader loader(client);
const unsigned added_position = queue_length; const unsigned added_position = queue_length;
@@ -363,49 +347,6 @@ handle_prioid(Client &client, Request args, [[maybe_unused]] Response &r)
return CommandResult::OK; return CommandResult::OK;
} }
static unsigned
ParseMoveDestination(const char *s, const RangeArg range,
const playlist &p)
{
assert(!range.IsEmpty());
assert(!range.IsOpenEnded());
const unsigned queue_length = p.queue.GetLength();
if (*s == '+') {
/* after the current song */
unsigned current = RequireCurrentPosition(p);
assert(current < queue_length);
if (range.Contains(current))
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
if (current >= range.end)
current -= range.Count();
return current + 1 +
ParseCommandArgUnsigned(s + 1,
queue_length - current - range.Count());
} else if (*s == '-') {
/* before the current song */
unsigned current = RequireCurrentPosition(p);
assert(current < queue_length);
if (range.Contains(current))
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
if (current >= range.end)
current -= range.Count();
return current -
ParseCommandArgUnsigned(s + 1,
queue_length - current - range.Count());
} else
/* absolute position */
return ParseCommandArgUnsigned(s,
queue_length - range.Count());
}
static CommandResult static CommandResult
handle_move(Partition &partition, RangeArg range, const char *to) handle_move(Partition &partition, RangeArg range, const char *to)
{ {

View File

@@ -31,8 +31,8 @@
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
#include "fs/List.hxx" #include "fs/List.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/io/FileReader.hxx" #include "io/FileReader.hxx"
#include "fs/io/BufferedReader.hxx" #include "io/BufferedReader.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <cassert> #include <cassert>

View File

@@ -23,11 +23,10 @@
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "fs/StandardDirectory.hxx" #include "fs/StandardDirectory.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringView.hxx"
#include <cassert> #include <cassert>
#include <string.h>
#ifndef _WIN32 #ifndef _WIN32
#include <pwd.h> #include <pwd.h>
@@ -96,30 +95,18 @@ ParsePath(const char *path)
if (*path == '\0') if (*path == '\0')
return GetConfiguredHome(); return GetConfiguredHome();
AllocatedPath home = nullptr;
if (*path == '/') { if (*path == '/') {
home = GetConfiguredHome();
++path; ++path;
return GetConfiguredHome() /
AllocatedPath::FromUTF8Throw(path);
} else { } else {
const char *slash = std::strchr(path, '/'); const auto [user, rest] =
const char *end = slash == nullptr StringView{path}.Split('/');
? path + strlen(path)
: slash;
const std::string user(path, end);
home = GetHome(user.c_str());
if (slash == nullptr) return GetHome(std::string{user}.c_str())
return home; / AllocatedPath::FromUTF8Throw(rest);
path = slash + 1;
} }
if (home.IsNull())
return nullptr;
return home / AllocatedPath::FromUTF8Throw(path);
} else if (!PathTraitsUTF8::IsAbsolute(path)) { } else if (!PathTraitsUTF8::IsAbsolute(path)) {
throw FormatRuntimeError("not an absolute path: %s", path); throw FormatRuntimeError("not an absolute path: %s", path);
} else { } else {

View File

@@ -22,6 +22,7 @@
#include "PlaylistFile.hxx" #include "PlaylistFile.hxx"
#include "Interface.hxx" #include "Interface.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "protocol/Ack.hxx"
#include <functional> #include <functional>
@@ -41,3 +42,41 @@ search_add_to_playlist(const Database &db, const Storage *storage,
const auto f = [=](auto && arg1) { return AddSong(storage, playlist_path_utf8, arg1); }; const auto f = [=](auto && arg1) { return AddSong(storage, playlist_path_utf8, arg1); };
db.Visit(selection, f); db.Visit(selection, f);
} }
unsigned
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
const DatabaseSelection &selection,
PlaylistFileEditor &playlist,
unsigned position)
{
assert(position <= playlist.size());
unsigned n = 0;
db.Visit(selection, [&playlist, position, &n, storage](const auto &song){
playlist.Insert(position + n,
DatabaseDetachSong(storage, song));
++n;
});
return n;
}
void
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
const DatabaseSelection &selection,
const char *playlist_name,
unsigned position)
{
PlaylistFileEditor editor{
playlist_name,
PlaylistFileEditor::LoadMode::TRY,
};
if (position > editor.size())
throw ProtocolError{ACK_ERROR_ARG, "Bad position"};
if (SearchInsertIntoPlaylist(db, storage, selection,
editor, position) > 0)
editor.Save();
}

View File

@@ -25,6 +25,7 @@
class Database; class Database;
class Storage; class Storage;
struct DatabaseSelection; struct DatabaseSelection;
class PlaylistFileEditor;
gcc_nonnull(3) gcc_nonnull(3)
void void
@@ -32,4 +33,19 @@ search_add_to_playlist(const Database &db, const Storage *storage,
const char *playlist_path_utf8, const char *playlist_path_utf8,
const DatabaseSelection &selection); const DatabaseSelection &selection);
/**
* @return the number of songs added
*/
unsigned
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
const DatabaseSelection &selection,
PlaylistFileEditor &playlist,
unsigned position);
void
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
const DatabaseSelection &selection,
const char *playlist_name,
unsigned position);
#endif #endif

View File

@@ -35,6 +35,7 @@ db_plugins = static_library(
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
upnp_dep, upnp_dep,
pcre_dep,
libmpdclient_dep, libmpdclient_dep,
log_dep, log_dep,
], ],

View File

@@ -20,8 +20,8 @@
#include "DatabaseSave.hxx" #include "DatabaseSave.hxx"
#include "db/DatabaseLock.hxx" #include "db/DatabaseLock.hxx"
#include "DirectorySave.hxx" #include "DirectorySave.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "fs/io/TextFile.hxx" #include "io/LineReader.hxx"
#include "tag/ParseName.hxx" #include "tag/ParseName.hxx"
#include "tag/Settings.hxx" #include "tag/Settings.hxx"
#include "fs/Charset.hxx" #include "fs/Charset.hxx"
@@ -64,7 +64,7 @@ db_save_internal(BufferedOutputStream &os, const Directory &music_root)
} }
void void
db_load_internal(TextFile &file, Directory &music_root) db_load_internal(LineReader &file, Directory &music_root)
{ {
char *line; char *line;
unsigned format = 0; unsigned format = 0;

View File

@@ -22,7 +22,7 @@
struct Directory; struct Directory;
class BufferedOutputStream; class BufferedOutputStream;
class TextFile; class LineReader;
void void
db_save_internal(BufferedOutputStream &os, const Directory &root); db_save_internal(BufferedOutputStream &os, const Directory &root);
@@ -31,6 +31,6 @@ db_save_internal(BufferedOutputStream &os, const Directory &root);
* Throws #std::runtime_error on error. * Throws #std::runtime_error on error.
*/ */
void void
db_load_internal(TextFile &file, Directory &root); db_load_internal(LineReader &file, Directory &root);
#endif #endif

View File

@@ -278,5 +278,5 @@ Directory::Walk(bool recursive, const SongFilter *filter,
LightDirectory LightDirectory
Directory::Export() const noexcept Directory::Export() const noexcept
{ {
return LightDirectory(GetPath(), mtime); return {GetPath(), mtime};
} }

View File

@@ -23,8 +23,8 @@
#include "SongSave.hxx" #include "SongSave.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "PlaylistDatabase.hxx" #include "PlaylistDatabase.hxx"
#include "fs/io/TextFile.hxx" #include "io/LineReader.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "time/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
@@ -121,7 +121,7 @@ ParseLine(Directory &directory, const char *line)
} }
static Directory * static Directory *
directory_load_subdir(TextFile &file, Directory &parent, std::string_view name) directory_load_subdir(LineReader &file, Directory &parent, std::string_view name)
{ {
if (parent.FindChild(name) != nullptr) if (parent.FindChild(name) != nullptr)
throw FormatRuntimeError("Duplicate subdirectory '%.*s'", throw FormatRuntimeError("Duplicate subdirectory '%.*s'",
@@ -152,7 +152,7 @@ directory_load_subdir(TextFile &file, Directory &parent, std::string_view name)
} }
void void
directory_load(TextFile &file, Directory &directory) directory_load(LineReader &file, Directory &directory)
{ {
const char *line; const char *line;

View File

@@ -21,7 +21,7 @@
#define MPD_DIRECTORY_SAVE_HXX #define MPD_DIRECTORY_SAVE_HXX
struct Directory; struct Directory;
class TextFile; class LineReader;
class BufferedOutputStream; class BufferedOutputStream;
void void
@@ -31,6 +31,6 @@ directory_save(BufferedOutputStream &os, const Directory &directory);
* Throws #std::runtime_error on error. * Throws #std::runtime_error on error.
*/ */
void void
directory_load(TextFile &file, Directory &directory); directory_load(LineReader &file, Directory &directory);
#endif #endif

View File

@@ -34,8 +34,8 @@
#include "db/DatabaseLock.hxx" #include "db/DatabaseLock.hxx"
#include "db/DatabaseError.hxx" #include "db/DatabaseError.hxx"
#include "fs/io/TextFile.hxx" #include "fs/io/TextFile.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "fs/io/FileOutputStream.hxx" #include "io/FileOutputStream.hxx"
#include "fs/FileInfo.hxx" #include "fs/FileInfo.hxx"
#include "config/Block.hxx" #include "config/Block.hxx"
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
@@ -46,7 +46,7 @@
#include "Log.hxx" #include "Log.hxx"
#ifdef ENABLE_ZLIB #ifdef ENABLE_ZLIB
#include "fs/io/GzipOutputStream.hxx" #include "lib/zlib/GzipOutputStream.hxx"
#endif #endif
#include <cerrno> #include <cerrno>
@@ -316,7 +316,7 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
if (r.rest.find('/') == std::string_view::npos) { if (r.rest.find('/') == std::string_view::npos) {
if (visit_song) { if (visit_song) {
Song *song = r.directory->FindSong(r.rest); const Song *song = r.directory->FindSong(r.rest);
if (song != nullptr) { if (song != nullptr) {
const auto song2 = song->Export(); const auto song2 = song->Export();
if (selection.Match(song2)) if (selection.Match(song2))

View File

@@ -39,6 +39,7 @@
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/RecursiveMap.hxx" #include "util/RecursiveMap.hxx"
#include "util/SplitString.hxx" #include "util/SplitString.hxx"
#include "config/Block.hxx"
#include <cassert> #include <cassert>
#include <string> #include <string>
@@ -76,10 +77,13 @@ class UpnpDatabase : public Database {
UpnpClient_Handle handle; UpnpClient_Handle handle;
UPnPDeviceDirectory *discovery; UPnPDeviceDirectory *discovery;
const char* interface;
public: public:
explicit UpnpDatabase(EventLoop &_event_loop) noexcept explicit UpnpDatabase(EventLoop &_event_loop, const ConfigBlock &block) noexcept
:Database(upnp_db_plugin), :Database(upnp_db_plugin),
event_loop(_event_loop) {} event_loop(_event_loop),
interface(block.GetBlockValue("interface", nullptr)) {}
static DatabasePtr Create(EventLoop &main_event_loop, static DatabasePtr Create(EventLoop &main_event_loop,
EventLoop &io_event_loop, EventLoop &io_event_loop,
@@ -147,15 +151,15 @@ private:
DatabasePtr DatabasePtr
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop, UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
[[maybe_unused]] DatabaseListener &listener, [[maybe_unused]] DatabaseListener &listener,
const ConfigBlock &) noexcept const ConfigBlock &block) noexcept
{ {
return std::make_unique<UpnpDatabase>(io_event_loop); return std::make_unique<UpnpDatabase>(io_event_loop, block);;
} }
void void
UpnpDatabase::Open() UpnpDatabase::Open()
{ {
handle = UpnpClientGlobalInit(); handle = UpnpClientGlobalInit(interface);
discovery = new UPnPDeviceDirectory(event_loop, handle); discovery = new UPnPDeviceDirectory(event_loop, handle);
try { try {
@@ -246,11 +250,11 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
{ {
const SongFilter *filter = selection.filter; const SongFilter *filter = selection.filter;
if (selection.filter == nullptr) if (selection.filter == nullptr)
return UPnPDirContent(); return {};
const auto searchcaps = server.getSearchCapabilities(handle); const auto searchcaps = server.getSearchCapabilities(handle);
if (searchcaps.empty()) if (searchcaps.empty())
return UPnPDirContent(); return {};
std::string cond; std::string cond;
for (const auto &item : filter->GetItems()) { for (const auto &item : filter->GetItems()) {

View File

@@ -33,7 +33,6 @@
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <string>
#include <exception> #include <exception>
#include <string.h> #include <string.h>

View File

@@ -282,7 +282,7 @@ InotifyUpdate::InotifyCallback(int wd, unsigned mask,
(mask & IN_ISDIR) != 0) { (mask & IN_ISDIR) != 0) {
/* a sub directory was changed: register those in /* a sub directory was changed: register those in
inotify */ inotify */
const Path root_path = root->name; const auto root_path = root->name;
const auto path_fs = uri_fs.IsNull() const auto path_fs = uri_fs.IsNull()
? root_path ? root_path

View File

@@ -34,7 +34,7 @@ UpdateQueueItem
UpdateQueue::Pop() noexcept UpdateQueue::Pop() noexcept
{ {
if (update_queue.empty()) if (update_queue.empty())
return UpdateQueueItem(); return {};
auto i = std::move(update_queue.front()); auto i = std::move(update_queue.front());
update_queue.pop_front(); update_queue.pop_front();

View File

@@ -36,7 +36,7 @@ UpdateRemoveService::RunDeferred() noexcept
std::forward_list<std::string> copy; std::forward_list<std::string> copy;
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
std::swap(uris, copy); std::swap(uris, copy);
} }
@@ -55,7 +55,7 @@ UpdateRemoveService::Remove(std::string &&uri)
bool was_empty; bool was_empty;
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
was_empty = uris.empty(); was_empty = uris.empty();
uris.emplace_front(std::move(uri)); uris.emplace_front(std::move(uri));
} }

View File

@@ -84,7 +84,7 @@ try {
} }
bool bool
directory_child_access(Storage &storage, const Directory &directory, directory_child_access(const Storage &storage, const Directory &directory,
std::string_view name, int mode) noexcept std::string_view name, int mode) noexcept
{ {
#ifdef _WIN32 #ifdef _WIN32

View File

@@ -55,7 +55,7 @@ directory_child_is_regular(Storage &storage, const Directory &directory,
*/ */
[[gnu::pure]] [[gnu::pure]]
bool bool
directory_child_access(Storage &storage, const Directory &directory, directory_child_access(const Storage &storage, const Directory &directory,
std::string_view name, int mode) noexcept; std::string_view name, int mode) noexcept;
#endif #endif

View File

@@ -188,8 +188,8 @@ UpdateWalk::UpdateRegularFile(Directory &directory,
const char *name, const char *name,
const StorageFileInfo &info) noexcept const StorageFileInfo &info) noexcept
{ {
const auto suffix = uri_get_suffix(name); const char *suffix = PathTraitsUTF8::GetFilenameSuffix(name);
if (suffix.empty()) if (suffix == nullptr)
return false; return false;
return UpdateSongFile(directory, name, suffix, info) || return UpdateSongFile(directory, name, suffix, info) ||

View File

@@ -152,7 +152,7 @@ DecoderBridge::FlushChunk() noexcept
if (!chunk->IsEmpty()) if (!chunk->IsEmpty())
dc.pipe->Push(std::move(chunk)); dc.pipe->Push(std::move(chunk));
const std::lock_guard<Mutex> protect(dc.mutex); const std::scoped_lock<Mutex> protect(dc.mutex);
dc.client_cond.notify_one(); dc.client_cond.notify_one();
} }
@@ -214,7 +214,7 @@ DecoderBridge::GetVirtualCommand() noexcept
DecoderCommand DecoderCommand
DecoderBridge::LockGetVirtualCommand() noexcept DecoderBridge::LockGetVirtualCommand() noexcept
{ {
const std::lock_guard<Mutex> protect(dc.mutex); const std::scoped_lock<Mutex> protect(dc.mutex);
return GetVirtualCommand(); return GetVirtualCommand();
} }
@@ -274,7 +274,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
seekable); seekable);
{ {
const std::lock_guard<Mutex> protect(dc.mutex); const std::scoped_lock<Mutex> protect(dc.mutex);
dc.SetReady(audio_format, seekable, duration); dc.SetReady(audio_format, seekable, duration);
} }
@@ -300,7 +300,7 @@ DecoderBridge::GetCommand() noexcept
void void
DecoderBridge::CommandFinished() noexcept DecoderBridge::CommandFinished() noexcept
{ {
const std::lock_guard<Mutex> protect(dc.mutex); const std::scoped_lock<Mutex> protect(dc.mutex);
assert(dc.command != DecoderCommand::NONE || initial_seek_running); assert(dc.command != DecoderCommand::NONE || initial_seek_running);
assert(dc.command != DecoderCommand::SEEK || assert(dc.command != DecoderCommand::SEEK ||

View File

@@ -22,7 +22,7 @@
#include "Command.hxx" #include "Command.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "MixRampInfo.hxx" #include "tag/MixRampInfo.hxx"
#include "input/Handler.hxx" #include "input/Handler.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
@@ -231,7 +231,7 @@ public:
[[gnu::pure]] [[gnu::pure]]
bool LockIsIdle() const noexcept { bool LockIsIdle() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return IsIdle(); return IsIdle();
} }
@@ -241,7 +241,7 @@ public:
[[gnu::pure]] [[gnu::pure]]
bool LockIsStarting() const noexcept { bool LockIsStarting() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return IsStarting(); return IsStarting();
} }
@@ -253,7 +253,7 @@ public:
[[gnu::pure]] [[gnu::pure]]
bool LockHasFailed() const noexcept { bool LockHasFailed() const noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
return HasFailed(); return HasFailed();
} }
@@ -284,7 +284,7 @@ public:
* Like CheckRethrowError(), but locks and unlocks the object. * Like CheckRethrowError(), but locks and unlocks the object.
*/ */
void LockCheckRethrowError() const { void LockCheckRethrowError() const {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
CheckRethrowError(); CheckRethrowError();
} }
@@ -360,7 +360,7 @@ private:
} }
void LockAsynchronousCommand(DecoderCommand cmd) noexcept { void LockAsynchronousCommand(DecoderCommand cmd) noexcept {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
command = cmd; command = cmd;
Signal(); Signal();
} }

View File

@@ -35,8 +35,8 @@
#include "DecoderPlugin.hxx" #include "DecoderPlugin.hxx"
#include "ReplayGainInfo.hxx" #include "ReplayGainInfo.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "tag/MixRampInfo.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "MixRampInfo.hxx"
#include "config/Block.hxx" #include "config/Block.hxx"
#include "Chrono.hxx" #include "Chrono.hxx"

View File

@@ -20,7 +20,7 @@
#ifndef MPD_DECODER_READER_HXX #ifndef MPD_DECODER_READER_HXX
#define MPD_DECODER_READER_HXX #define MPD_DECODER_READER_HXX
#include "fs/io/Reader.hxx" #include "io/Reader.hxx"
class DecoderClient; class DecoderClient;
class InputStream; class InputStream;

View File

@@ -262,7 +262,7 @@ static void
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is) MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
{ {
{ {
const std::lock_guard<Mutex> protect(bridge.dc.mutex); const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF) if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
/* ReplayGain is disabled */ /* ReplayGain is disabled */
return; return;
@@ -337,7 +337,7 @@ TryDecoderFile(DecoderBridge &bridge, Path path_fs, std::string_view suffix,
DecoderControl &dc = bridge.dc; DecoderControl &dc = bridge.dc;
if (plugin.file_decode != nullptr) { if (plugin.file_decode != nullptr) {
const std::lock_guard<Mutex> protect(dc.mutex); const std::scoped_lock<Mutex> protect(dc.mutex);
return decoder_file_decode(plugin, bridge, path_fs); return decoder_file_decode(plugin, bridge, path_fs);
} else if (plugin.stream_decode != nullptr) { } else if (plugin.stream_decode != nullptr) {
std::unique_lock<Mutex> lock(dc.mutex); std::unique_lock<Mutex> lock(dc.mutex);
@@ -365,7 +365,7 @@ TryContainerDecoder(DecoderBridge &bridge, Path path_fs,
bridge.Reset(); bridge.Reset();
DecoderControl &dc = bridge.dc; DecoderControl &dc = bridge.dc;
const std::lock_guard<Mutex> protect(dc.mutex); const std::scoped_lock<Mutex> protect(dc.mutex);
return decoder_file_decode(plugin, bridge, path_fs); return decoder_file_decode(plugin, bridge, path_fs);
} }
@@ -395,10 +395,12 @@ TryContainerDecoder(DecoderBridge &bridge, Path path_fs,
static bool static bool
decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs) decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs)
{ {
const auto suffix = uri_get_suffix(uri_utf8); const char *_suffix = PathTraitsUTF8::GetFilenameSuffix(uri_utf8);
if (suffix.empty()) if (_suffix == nullptr)
return false; return false;
const std::string_view suffix{_suffix};
InputStreamPtr input_stream; InputStreamPtr input_stream;
try { try {

View File

@@ -83,8 +83,8 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) noexcept
static AFfileoffset static AFfileoffset
audiofile_file_length(AFvirtualfile *vfile) noexcept audiofile_file_length(AFvirtualfile *vfile) noexcept
{ {
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is; const InputStream &is = afis.is;
return is.GetSize(); return is.GetSize();
} }
@@ -92,8 +92,8 @@ audiofile_file_length(AFvirtualfile *vfile) noexcept
static AFfileoffset static AFfileoffset
audiofile_file_tell(AFvirtualfile *vfile) noexcept audiofile_file_tell(AFvirtualfile *vfile) noexcept
{ {
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is; const InputStream &is = afis.is;
return is.GetOffset(); return is.GetOffset();
} }

View File

@@ -38,7 +38,7 @@
#include "tag/Builder.hxx" #include "tag/Builder.hxx"
#include "tag/Handler.hxx" #include "tag/Handler.hxx"
#include "tag/ReplayGain.hxx" #include "tag/ReplayGain.hxx"
#include "tag/MixRamp.hxx" #include "tag/MixRampParser.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "pcm/CheckAudioFormat.hxx" #include "pcm/CheckAudioFormat.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
@@ -502,7 +502,7 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
FmtDebug(ffmpeg_domain, "codec '{}'", FmtDebug(ffmpeg_domain, "codec '{}'",
codec_descriptor->name); codec_descriptor->name);
AVCodec *codec = avcodec_find_decoder(codec_params.codec_id); const AVCodec *codec = avcodec_find_decoder(codec_params.codec_id);
if (!codec) { if (!codec) {
LogError(ffmpeg_domain, "Unsupported audio codec"); LogError(ffmpeg_domain, "Unsupported audio codec");

View File

@@ -21,6 +21,7 @@
#define __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS
#include "FfmpegIo.hxx" #include "FfmpegIo.hxx"
#include "libavutil/mem.h"
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
@@ -35,7 +36,11 @@ AvioStream::~AvioStream()
inline int inline int
AvioStream::Read(void *dest, int size) AvioStream::Read(void *dest, int size)
{ {
return decoder_read(client, input, dest, size); const auto nbytes = decoder_read(client, input, dest, size);
if (nbytes == 0)
return AVERROR_EOF;
return nbytes;
} }
inline int64_t inline int64_t

View File

@@ -78,7 +78,9 @@ FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc)
if (flac_parse_replay_gain(rgi, vc)) if (flac_parse_replay_gain(rgi, vc))
GetClient()->SubmitReplayGain(&rgi); GetClient()->SubmitReplayGain(&rgi);
GetClient()->SubmitMixRamp(flac_parse_mixramp(vc)); if (auto mix_ramp = flac_parse_mixramp(vc);
mix_ramp.IsDefined())
GetClient()->SubmitMixRamp(std::move(mix_ramp));
tag = flac_vorbis_comments_to_tag(&vc); tag = flac_vorbis_comments_to_tag(&vc);
} }

View File

@@ -23,9 +23,10 @@
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "tag/Id3Scan.hxx" #include "tag/Id3Scan.hxx"
#include "tag/Id3ReplayGain.hxx" #include "tag/Id3ReplayGain.hxx"
#include "tag/Id3MixRamp.hxx"
#include "tag/Handler.hxx" #include "tag/Handler.hxx"
#include "tag/ReplayGain.hxx" #include "tag/ReplayGain.hxx"
#include "tag/MixRamp.hxx" #include "tag/MixRampParser.hxx"
#include "pcm/CheckAudioFormat.hxx" #include "pcm/CheckAudioFormat.hxx"
#include "util/Clamp.hxx" #include "util/Clamp.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
@@ -268,35 +269,6 @@ MadDecoder::FillBuffer() noexcept
return true; return true;
} }
#ifdef ENABLE_ID3TAG
gcc_pure
static MixRampInfo
parse_id3_mixramp(struct id3_tag *tag) noexcept
{
MixRampInfo result;
struct id3_frame *frame;
for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
if (frame->nfields < 3)
continue;
char *const key = (char *)
id3_ucs4_latin1duplicate(id3_field_getstring
(&frame->fields[1]));
char *const value = (char *)
id3_ucs4_latin1duplicate(id3_field_getstring
(&frame->fields[2]));
ParseMixRampTag(result, key, value);
free(key);
free(value);
}
return result;
}
#endif
inline void inline void
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
{ {
@@ -310,7 +282,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
id3_data = stream.this_frame; id3_data = stream.this_frame;
mad_stream_skip(&(stream), tagsize); mad_stream_skip(&(stream), tagsize);
} else { } else {
allocated.reset(new id3_byte_t[tagsize]); allocated = std::make_unique<id3_byte_t[]>(tagsize);
memcpy(allocated.get(), stream.this_frame, count); memcpy(allocated.get(), stream.this_frame, count);
mad_stream_skip(&(stream), count); mad_stream_skip(&(stream), count);
@@ -338,7 +310,9 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
found_replay_gain = true; found_replay_gain = true;
} }
client->SubmitMixRamp(parse_id3_mixramp(id3_tag.get())); if (auto mix_ramp = Id3ToMixRampInfo(id3_tag.get());
mix_ramp.IsDefined())
client->SubmitMixRamp(std::move(mix_ramp));
} }
#else /* !ENABLE_ID3TAG */ #else /* !ENABLE_ID3TAG */

View File

@@ -23,7 +23,7 @@
#include "tag/Handler.hxx" #include "tag/Handler.hxx"
#include "tag/Builder.hxx" #include "tag/Builder.hxx"
#include "tag/ReplayGain.hxx" #include "tag/ReplayGain.hxx"
#include "tag/MixRamp.hxx" #include "tag/MixRampParser.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"

View File

@@ -91,7 +91,8 @@ ScanOpusTags(const void *data, size_t size,
if (!r.Expect("OpusTags", 8)) if (!r.Expect("OpusTags", 8))
return false; return false;
if (!handler.WantPair() && !handler.WantTag()) if (!handler.WantPair() && !handler.WantTag() &&
!handler.WantPicture())
return true; return true;
if (!r.SkipString()) if (!r.SkipString())

View File

@@ -27,7 +27,7 @@
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "lib/icu/Converter.hxx" #include "lib/icu/Converter.hxx"
#ifdef HAVE_SIDPLAYFP #ifdef HAVE_SIDPLAYFP
#include "fs/io/FileReader.hxx" #include "io/FileReader.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#endif #endif
#include "util/StringFormat.hxx" #include "util/StringFormat.hxx"
@@ -137,7 +137,7 @@ SidplayGlobal::SidplayGlobal(const ConfigBlock &block)
const auto kernal_path = block.GetPath("kernal"); const auto kernal_path = block.GetPath("kernal");
if (!kernal_path.IsNull()) if (!kernal_path.IsNull())
{ {
kernal.reset(new uint8_t[rom_size]); kernal = std::make_unique<uint8_t[]>(rom_size);
loadRom(kernal_path, kernal.get()); loadRom(kernal_path, kernal.get());
} }
@@ -145,7 +145,7 @@ SidplayGlobal::SidplayGlobal(const ConfigBlock &block)
const auto basic_path = block.GetPath("basic"); const auto basic_path = block.GetPath("basic");
if (!basic_path.IsNull()) if (!basic_path.IsNull())
{ {
basic.reset(new uint8_t[rom_size]); basic = std::make_unique<uint8_t[]>(rom_size);
loadRom(basic_path, basic.get()); loadRom(basic_path, basic.get());
} }
#endif #endif

View File

@@ -19,7 +19,7 @@
#include "ToOutputStream.hxx" #include "ToOutputStream.hxx"
#include "EncoderInterface.hxx" #include "EncoderInterface.hxx"
#include "fs/io/OutputStream.hxx" #include "io/OutputStream.hxx"
void void
EncoderToOutputStream(OutputStream &os, Encoder &encoder) EncoderToOutputStream(OutputStream &os, Encoder &encoder)

View File

@@ -68,7 +68,7 @@ private:
exception = std::current_exception(); exception = std::current_exception();
} }
const std::lock_guard<Mutex> lock(mutex); const std::scoped_lock<Mutex> lock(mutex);
done = true; done = true;
cond.notify_one(); cond.notify_one();
} }

View File

@@ -52,6 +52,13 @@ EventLoop::EventLoop(
EventLoop::~EventLoop() noexcept EventLoop::~EventLoop() noexcept
{ {
#if defined(HAVE_URING) && !defined(NDEBUG)
/* if Run() was never called (maybe because startup failed and
an exception is pending), we need to destruct the
Uring::Manager here or else the assertions below fail */
uring.reset();
#endif
assert(defer.empty()); assert(defer.empty());
assert(idle.empty()); assert(idle.empty());
#ifdef HAVE_THREADED_EVENT_LOOP #ifdef HAVE_THREADED_EVENT_LOOP
@@ -175,6 +182,10 @@ EventLoop::HandleTimers() noexcept
void void
EventLoop::AddDefer(DeferEvent &d) noexcept EventLoop::AddDefer(DeferEvent &d) noexcept
{ {
#ifdef HAVE_THREADED_EVENT_LOOP
assert(!IsAlive() || IsInside());
#endif
defer.push_back(d); defer.push_back(d);
again = true; again = true;
} }
@@ -312,7 +323,7 @@ EventLoop::Run() noexcept
/* try to handle DeferEvents without WakeFD /* try to handle DeferEvents without WakeFD
overhead */ overhead */
{ {
const std::lock_guard<Mutex> lock(mutex); const std::scoped_lock<Mutex> lock(mutex);
HandleInject(); HandleInject();
#endif #endif
@@ -335,7 +346,7 @@ EventLoop::Run() noexcept
#ifdef HAVE_THREADED_EVENT_LOOP #ifdef HAVE_THREADED_EVENT_LOOP
{ {
const std::lock_guard<Mutex> lock(mutex); const std::scoped_lock<Mutex> lock(mutex);
busy = true; busy = true;
} }
#endif #endif
@@ -367,7 +378,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
bool must_wake; bool must_wake;
{ {
const std::lock_guard<Mutex> lock(mutex); const std::scoped_lock<Mutex> lock(mutex);
if (d.IsPending()) if (d.IsPending())
return; return;
@@ -386,7 +397,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
void void
EventLoop::RemoveInject(InjectEvent &d) noexcept EventLoop::RemoveInject(InjectEvent &d) noexcept
{ {
const std::lock_guard<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
if (d.IsPending()) if (d.IsPending())
inject.erase(inject.iterator_to(d)); inject.erase(inject.iterator_to(d));
@@ -413,7 +424,7 @@ EventLoop::OnSocketReady([[maybe_unused]] unsigned flags) noexcept
wake_fd.Read(); wake_fd.Read();
const std::lock_guard<Mutex> lock(mutex); const std::scoped_lock<Mutex> lock(mutex);
HandleInject(); HandleInject();
} }

View File

@@ -91,7 +91,7 @@ public:
} }
#endif #endif
bool IsDefined() const noexcept { [[nodiscard]] bool IsDefined() const noexcept {
return event.IsDefined(); return event.IsDefined();
} }

View File

@@ -37,6 +37,7 @@
#endif #endif
#include <algorithm> #include <algorithm>
#include <array>
#include <cassert> #include <cassert>
#include <csignal> #include <csignal>
@@ -62,7 +63,7 @@ public:
#endif #endif
} }
auto &GetEventLoop() const noexcept { [[nodiscard]] auto &GetEventLoop() const noexcept {
return event.GetEventLoop(); return event.GetEventLoop();
} }
@@ -90,12 +91,12 @@ private:
/* this should be enough - is it? */ /* this should be enough - is it? */
static constexpr unsigned MAX_SIGNAL = 64; static constexpr unsigned MAX_SIGNAL = 64;
static SignalHandler signal_handlers[MAX_SIGNAL]; static std::array<SignalHandler, MAX_SIGNAL> signal_handlers;
#ifdef USE_SIGNALFD #ifdef USE_SIGNALFD
static sigset_t signal_mask; static sigset_t signal_mask;
#else #else
static std::atomic_bool signal_pending[MAX_SIGNAL]; static std::array<std::atomic_bool, MAX_SIGNAL> signal_pending;
#endif #endif
static Manual<SignalMonitor> monitor; static Manual<SignalMonitor> monitor;
@@ -153,7 +154,7 @@ void
SignalMonitorFinish() noexcept SignalMonitorFinish() noexcept
{ {
#ifdef USE_SIGNALFD #ifdef USE_SIGNALFD
std::fill_n(signal_handlers, MAX_SIGNAL, nullptr); signal_handlers = {};
#else #else
struct sigaction sa; struct sigaction sa;
sa.sa_flags = 0; sa.sa_flags = 0;
@@ -167,7 +168,7 @@ SignalMonitorFinish() noexcept
} }
} }
std::fill_n(signal_pending, MAX_SIGNAL, false); std::fill(signal_pending.begin(), signal_pending.end(), false);
#endif #endif
monitor.Destruct(); monitor.Destruct();

View File

@@ -62,7 +62,7 @@ EventThread::Run() noexcept
SetThreadRealtime(); SetThreadRealtime();
} catch (...) { } catch (...) {
FmtInfo(event_domain, FmtInfo(event_domain,
"RTIOThread could not get realtime scheduling, continuing anyway: %s", "RTIOThread could not get realtime scheduling, continuing anyway: {}",
std::current_exception()); std::current_exception());
} }
} }

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