Compare commits

...

232 Commits

Author SHA1 Message Date
Max Kellermann
18628bf89e release v0.22.9 2021-06-23 20:56:13 +02:00
Yetangitu
2052b461af Fix android build error when confronted with package versions ending in +revision_information
The script seems to assume package version numbers always end in numeric versions with an optional alpha-suffix. Alas, were it only so simple... Sometimes the package is called fizzbang-1.2.3+release_info in which case the build fails. No more!

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1177
2021-06-23 20:53:46 +02:00
Max Kellermann
5019bdcd52 TagAny: invoke ScanGenericTags() on remote files
This fixes reading ID3 tags on remote files with the commands
"readcomments" and "readpicture".

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1180
2021-06-23 20:49:30 +02:00
Naglis Jonaitis
8be0bcbdb9 doc/plugins.rst: mention default libsamplerate type 2021-06-23 15:51:31 +02:00
Naglis Jonaitis
af72a22ed8 doc/user.rst: document restore_paused 2021-06-23 15:50:41 +02:00
Naglis Jonaitis
6ed9668fea doc, README.md: update IRC server name/URL 2021-06-23 15:48:42 +02:00
Max Kellermann
175d2c6d29 Main: use AtScopeExit() to call ZeroconfDeinit()
Make sure that ZeroconfDeinit() gets called even if startup fails with
an exception.  Fixes an assertion failure because an Avahi TimerEvent
is still active.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1192
2021-06-22 20:31:45 +02:00
Max Kellermann
ab487b9a99 Android: use startForegroundService() in Android 8+
Fixes the error:

 IllegalStateException: Not allowed to start service Intent { cmp=org.musicpd/.Main (has extras) }: app is in background
2021-05-31 20:45:31 +02:00
Max Kellermann
ac59ec34f9 decoder/ffmpeg: fix build failure with FFmpeg 3.4
av_demuxer_iterate() was added in libavformat 58.9.100.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1178
2021-05-31 18:10:06 +02:00
Max Kellermann
82da57b7ce decoder/ffmpeg: suppress -Wunused with libavformat<58.6.100 2021-05-31 16:49:48 +02:00
Max Kellermann
aa6dac9bd2 db/proxy: suppress -Wunused with libmpdclient<2.12 2021-05-31 16:49:08 +02:00
Max Kellermann
a26bf261a9 input/last: call Close() in Open()
Prevents a possible bug which occurs when the caller-provided open()
function throws; then the "uri" field is never set.
2021-05-27 14:04:28 +02:00
Max Kellermann
c692286c67 input/last: clear "uri" field in Close()
Prevent false negative after the stream was closed automatically after
20 seconds.
2021-05-27 14:03:33 +02:00
Max Kellermann
3775766605 NEWS: mention new FFmpeg/ID3v2 tags 2021-05-26 13:07:03 +02:00
Max Kellermann
38e24208f6 decoder/ffmpeg: support the tags "album-sort", "artist-sort" 2021-05-26 13:04:47 +02:00
Max Kellermann
fbaedf2262 decoder/ffmpeg: support the "sort_album" tag
From libavformat/mov.c.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1173
2021-05-26 13:03:47 +02:00
Max Kellermann
8f3341cefb decoder/ffmpeg: add comment 2021-05-26 13:03:41 +02:00
Max Kellermann
4ec4bab3a9 decoder/ffmpeg: remove "year" tag
This mapping was added 11 years ago in commit 766b9fd453, but FFmpeg
doesn't appear to support it.
2021-05-26 13:03:27 +02:00
Max Kellermann
6d567bcd35 decoder/ffmpeg: fix ArtistSort and AlbumArtistSort mapping
These were added 11 years ago in commit 766b9fd453, but I cannot find
any evidence in the FFmpeg repository that these names were ever
supported.  This commit adds the tags as they are currently present in
libavformat/mov.c.
2021-05-26 13:03:26 +02:00
Max Kellermann
363d9f0180 db/update/Walk: load all .mpdignore files of all parent directories
When updating everything, this did work, but if updating only a
subdirectory, the ".mpdignore" in the parents were not used.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1172
2021-05-25 22:42:44 +02:00
Max Kellermann
db0682a469 db/update/Walk: move code to LoadExcludeList() 2021-05-25 22:38:01 +02:00
Max Kellermann
7a6823dcdf zeroconf/AvahiPoll: the struct timeval is an absolute time point
Fixes broken libavahi-client timeouts.
2021-05-25 22:25:45 +02:00
Max Kellermann
bce144a232 zeroconf/AvahiPoll: move code to Schedule() 2021-05-25 22:23:55 +02:00
Max Kellermann
0cef84cac6 zeroconf/AvahiPoll: rename "timer" to "event" 2021-05-25 22:23:55 +02:00
Max Kellermann
56c0733b42 meson.build: disable -Wsuggest-override with GCC 8 2021-05-25 22:23:55 +02:00
Max Kellermann
0b0acb3981 meson.build: add more C++ warning flags 2021-05-25 22:03:49 +02:00
Max Kellermann
1375dcc4ec meson.build: sort warning options 2021-05-25 21:49:03 +02:00
Max Kellermann
6aeb0e335b meson.build: add comment for -Wno-non-virtual-dtor 2021-05-25 21:48:19 +02:00
Max Kellermann
c1e2537851 meson.build: add comment for clang-only warning options 2021-05-25 21:45:39 +02:00
Max Kellermann
8c690fb737 decoder/mad: move variable declaration into "case" 2021-05-25 21:34:09 +02:00
Max Kellermann
dad1c21b59 zeroconf/avahi: move variable declaration into "case" 2021-05-25 21:34:09 +02:00
Max Kellermann
dd10b2bd61 meson.build: remove warning options implied by -Wall -Wextra 2021-05-25 21:24:44 +02:00
Max Kellermann
48c7c540df meson.build: use add_project_arguments() instead of add_global_arguments()
Don't propagate MPD-specific compiler flags to subprojects.
2021-05-25 21:08:06 +02:00
Max Kellermann
281270cd2a meson.build: remove unused variables common_cflags, common_cxxflags 2021-05-25 21:07:05 +02:00
Max Kellermann
02502514f6 meson.build: require clang 7 (remove bug workaround) 2021-05-25 21:06:55 +02:00
Max Kellermann
1bc02123f9 meson.build: remove "-pedantic", implied by Meson
Meson adds "-Wpedantic" in warning_level 3 (which is MPD's default).
2021-05-25 21:01:15 +02:00
Max Kellermann
3488a47c41 subprojects/sqlite3.wrap: add SQLite wrap 2021-05-25 20:51:03 +02:00
Max Kellermann
fd82d67678 sticker/Database: pass NarrowPath to sqlite3_open()
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1171
2021-05-25 18:45:45 +02:00
Max Kellermann
e66c12105b lib/sqlite/meson.build: add missing external dependency on libsqlite 2021-05-25 18:41:43 +02:00
Namkhai B
dbe12a6b90 util/RuntimeError: Disable format-security for gcc
Fixes building under GCC 11
2021-05-25 18:19:19 +02:00
Philippe Antoine
d3a680cc87 meson: set only sanitizers for fuzzer when unspecified
That is when meson option b_sanitize is not used
2021-05-24 09:03:16 +02:00
Max Kellermann
62fc4d5cf4 increment version number to 0.22.9 2021-05-24 09:03:07 +02:00
Max Kellermann
14465be847 release v0.22.8 2021-05-22 17:33:36 +02:00
Max Kellermann
0e49de867d input/last: add nullptr check to Open(), fixes assertion failure
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1168
2021-05-22 17:33:25 +02:00
Max Kellermann
f2e4529707 increment version number to 0.22.8 2021-05-22 17:32:00 +02:00
Max Kellermann
3547fc7e61 release v0.22.7 2021-05-19 18:13:26 +02:00
Max Kellermann
466a05bc52 CommandLine: update copyright year in --version output 2021-05-19 18:09:38 +02:00
Max Kellermann
6de4064cca client/Response, command/file: use %lu instead of %zu on Windows
Fixes -Wformat warnings.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1150
2021-05-19 18:06:40 +02:00
Max Kellermann
bcf0fdd3a8 meson.build: define NOUSER on Windows for lighter windows.h
A few exceptions are needed for sources which include COM header
(directly or indirectly).

This fixes lots of shadow warnings, see
https://github.com/MusicPlayerDaemon/MPD/issues/1150
2021-05-19 18:02:49 +02:00
Max Kellermann
a8f05a7efc win32/HResult: un-inline HRESULTToString() to reduce header dependencies 2021-05-19 17:48:42 +02:00
Max Kellermann
c64a3b5dbb fs/Glob: un-inline the Windows version to reduce header dependencies 2021-05-19 17:41:23 +02:00
Max Kellermann
16c38c438f fs/Glob: use defaulted move constructor 2021-05-19 17:40:23 +02:00
Max Kellermann
48cc4a6ced fs/Glob: remove redundant #ifdefs 2021-05-19 17:40:03 +02:00
Max Kellermann
a169a05e41 win32, ...: avoid including windows.h
Include the most specific header documented by MSDN instead.
2021-05-19 17:25:32 +02:00
Max Kellermann
a6cb3139db meson.build: disable Windows header features not needed by MPD 2021-05-19 17:16:16 +02:00
Max Kellermann
239a83324e meson.build: document Windows definitions 2021-05-19 17:15:25 +02:00
Max Kellermann
8efa5c7641 output/wasapi: use "%lu" in log calls
"%lu" is portable - it works with both POSIX and Microsoft flavors.

Fixes a part of https://github.com/MusicPlayerDaemon/MPD/issues/1150
2021-05-19 17:10:49 +02:00
Max Kellermann
28e7be248f util/RuntimeError: disable -Wformat-security as a kludge 2021-05-19 14:57:20 +02:00
Max Kellermann
c3f9b38c97 command/PlaylistCommands: pass real_uri to LookupRemoteTag()
For querying tags, the real song URI should be used, because if the
(display) URI is different, requesting it will not produce a usable
response.  This is a theoretical problem because none of the existing
playlist plugins sets the real_uri.

This requires changing the URI comparison in playlist::TagModified().

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1154
2021-05-18 21:35:09 +02:00
Max Kellermann
dbb18a401b command/file: cache the last "albumart" file
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1156
2021-05-18 17:04:09 +02:00
Max Kellermann
e1e41708af input/LastInputStream: new class 2021-05-18 17:04:09 +02:00
Max Kellermann
638dfc3981 {input,storage}/curl: set CURLOPT_HTTPAUTH=CURLAUTH_BASIC
With the default value CURLAUTH_ANY, libcurl needs to probe for
authentication methods first, and only the second request will have an
Authorization header.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1155
2021-05-17 19:26:05 +02:00
Max Kellermann
7c09e44ad4 python/build/libs.py: update OpenSSL to 3.0.0-alpha16 2021-05-17 18:42:05 +02:00
Max Kellermann
365b798f33 python/build/libs.py: update FFmpeg to 4.4 2021-05-17 18:42:05 +02:00
Max Kellermann
6f51d910ee python/build/libs.py: update CURL to 7.76.1 2021-05-17 18:42:05 +02:00
Max Kellermann
1215818572 doc/meson.build: remove "upload" target
Since we migrated to readthedocs.io, we don't need this target
anymore.  And Meson 0.58.0 apparently has a change breaking this
target.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1161
2021-05-17 18:33:00 +02:00
skidoo23
514ed33a02 python/build: update Boost URL and version 2021-05-11 13:11:16 +02:00
Max Kellermann
bfed47b82d .travis.yml: switch the OSX build to xcode11.6
The clang/libc++ version in xcode10.3 does not support C++17 properly
and cannot build MPD.
2021-04-28 13:40:36 +02:00
Max Kellermann
8c51440057 test/test_mixramp: workaround for -Wdouble-promotion 2021-04-28 13:00:42 +02:00
Max Kellermann
018858ec97 .travis.yml: install standard Homebrew GTest formula 2021-04-27 16:23:23 +02:00
Max Kellermann
3c1988b68f .travis.yml: switch from Ubuntu Bionic to Ubuntu Focal (20.04) 2021-04-27 16:23:23 +02:00
Max Kellermann
5452428d69 .travis.yml: switch to ppa:ricotz/toolchain for ninja 1.8 on Ubuntu Trusty
The old "ppa:mstipicevic/ninja-build-1-7-2" just provides ninja 1.7
which is too old and breaks the build.
2021-04-27 16:23:23 +02:00
Max Kellermann
d6bf6e161a .travis.yml: remove obsolete comment 2021-04-27 15:48:19 +02:00
Max Kellermann
a71b76bb3c test/test_pcm_format: another workaround for -Wdouble-promotion 2021-04-26 23:25:36 +02:00
Max Kellermann
c1429500b2 test/test_pcm_format: work around -Wdouble-promotion 2021-04-26 22:21:12 +02:00
Max Kellermann
0f02bbc2fe output/jack: enable on Windows
This enables the JACK output plugin on Windows, but doesn't link
against libjack64.dll, instead loads the DLL at runtime with
LoadLibrary().  This kludge avoids the extremely fragile JACK shared
memory protocol by using the system's libjack64.dll, without requiring
the same DLL at build time.
2021-04-26 21:47:20 +02:00
Max Kellermann
b885f358a5 output/control: add missing nullptr checks
Fixes crash when pausing the default partition after an output was
moved to another partition.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1147
2021-04-26 21:34:58 +02:00
Max Kellermann
650a30d794 Revert "tag/Pool: use strncmp() without strlen() to compare strings"
This reverts commit 1532983fb5.  This
optimization was bad because now all strings match if they are a
prefix of another string, and this caused collisions in the tag string
pool, corrupting the database.
2021-04-15 16:15:44 +02:00
Max Kellermann
1dc71f383a python/build/boost.py: touch boost/version.hpp to avoid reinstalling all the time 2021-04-07 13:50:05 +02:00
John Regan
6dfebf7df9 gme: add support for rsn files
Upcoming release of game-music-emu will support it, details here: https://bitbucket.org/mpyne/game-music-emu/pull-requests/23/rsn-support
2021-03-13 08:40:25 +01:00
Shen-Ta Hsieh
4bcdcca7f5 output/wasapi: use calculated new buffer instead old one 2021-03-13 08:39:56 +01:00
bitkeeper
c08a8581ee Added cross-origin header to http headers of the http output.
The current http output doesn't provide a header for cross-origin support. This prevents to use the mpd http stream directly from an other webapplication due the origin from the webpage differs from then the audio stream.

The fix is to add the following header to the http response:
Access-Control-Allow-Origin: *
2021-03-10 21:27:19 +01:00
Max Kellermann
25b0194036 output/wasapi: implement Drain() 2021-03-10 21:05:48 +01:00
Max Kellermann
77fe727e69 output/wasapi: move the "is_started" flag to class WasapiOutputThread 2021-03-10 20:43:28 +01:00
Max Kellermann
73f9824ddf output/wasapi: eliminate friend declaration 2021-03-10 20:38:41 +01:00
Max Kellermann
1fe0c673bc output/wasapi: implement Cancel() properly
Calling consume_all() is illegal in the producer thread.
2021-03-10 20:38:27 +01:00
Max Kellermann
8a045207a7 output/wasapi: add field paused
Fixes bogus Delay() results at the start of playback, because Delay()
thinks the output is paused.
2021-03-10 20:09:37 +01:00
Max Kellermann
fe7c5a4208 output/wasapi: initialize is_started in Open() 2021-03-10 20:07:22 +01:00
Max Kellermann
8024f7e84d output/wasapi: move the thread->Play() call right before the consumed_size check
Fixes a bogus assertion failure (which can now be removed).
2021-03-10 20:07:19 +01:00
Max Kellermann
14f0134097 output/wasapi: make device_config const 2021-03-10 20:05:14 +01:00
Max Kellermann
1da27be84d output/wasapi: move runtime fields below configuration fields 2021-03-10 20:00:08 +01:00
Max Kellermann
08135f2cb7 output/wasapi: make configuration fields const 2021-03-10 19:58:33 +01:00
Max Kellermann
5907656bbb output/wasapi: stop the IAudioClient while paused
Instead of generating silence, do nothing, don't waste CPU time.
2021-03-10 17:48:49 +01:00
Max Kellermann
2ac2bd26f8 output/wasapi: combine two if statements to one switch 2021-03-10 17:45:54 +01:00
Max Kellermann
a2be91aea5 output/wasapi: add method WasapiOutputThread::InterruptWaiter() 2021-03-10 17:42:26 +01:00
Max Kellermann
579428172e output/wasapi: remove the broken Delay() calculation code
This code is complicated - and broken: the producer thread is not
allowed to call consumer methods.  Also the code is not necessary
because this plugin implements Interrupt().
2021-03-10 17:39:07 +01:00
Max Kellermann
3e484637f9 output/wasapi: rename OpenDevice() to ChooseDevice()
OpenDevice was a confusing name because it does not actually open a
device.
2021-03-10 17:34:10 +01:00
Max Kellermann
3e93c392d7 output/wasapi: make enumerator a local variable 2021-03-10 17:23:41 +01:00
Max Kellermann
0a97e68aa9 output/wasapi: start after the buffer has been filled
Postpone the Start() call until there is something to be played.
2021-03-08 23:03:25 +01:00
Max Kellermann
69783a44c8 output/wasapi: move Start()/Stop() calls to WasapiOutputThread::Work() 2021-03-08 22:58:20 +01:00
Max Kellermann
d72263d28d win32/HResult: support AUDCLNT_E_NOT_{INITIALIZED,STOPPED} 2021-03-08 22:57:44 +01:00
Max Kellermann
24a205a1aa win32/HResult: try to use FormatMessage() 2021-03-08 22:54:46 +01:00
Max Kellermann
3a948515ce output/wasapi: check for exceptions after Wait()
This finishes problems which occur early in the WasapiOutputThread;
previously, the error was ignored and the output blocked forever
without doing anything (and without reporting the error).
2021-03-08 22:46:40 +01:00
Max Kellermann
9ade93983c output/wasapi: rename method WaitDataPoped() to Wait() 2021-03-08 22:44:49 +01:00
Max Kellermann
6931ce9558 output/wasapi: make the Thread a field, not a base class 2021-03-08 22:30:19 +01:00
Max Kellermann
d6fb07a3e4 output/wasapi: start the WasapiOutputThread in its constructor 2021-03-08 22:29:33 +01:00
Max Kellermann
01d3c2705e output/wasapi: Finish() calls Join() 2021-03-08 22:28:36 +01:00
Max Kellermann
29346dc9c5 output/wasapi: remove the thread management code from DoDisable()
This is duplicate; this has already been done in Close().
2021-03-08 22:27:46 +01:00
Max Kellermann
d19b3df3b0 test/run_output: call AudioOutput::Drain() 2021-03-08 22:20:55 +01:00
Max Kellermann
798e68ef62 output/wasapi: don't clear the exception in CheckException()
This is pointless; the method cannot be called again anyway.
2021-03-08 22:18:48 +01:00
Max Kellermann
79397db5b4 output/wasapi: remove the "thrown" field
It is pointless to let WasapiOutputThread wait for the
CheckException() call.
2021-03-08 22:17:45 +01:00
Max Kellermann
9256190a9b output/wasapi: move catch block to the Work() function level
If an exception has been caught, the method cannot continue playback,
therefore it doesn't make sense to have the "catch" block inside the
"while" block (and not break the loop after catching an exception).
2021-03-08 22:15:36 +01:00
Max Kellermann
3a0dbb0a67 output/wasapi: make WasapiOutputThread::is_exclusive const 2021-03-08 22:09:23 +01:00
Max Kellermann
3d6c9d1b88 output/wasapi: catch all exception 2021-03-08 22:06:29 +01:00
Max Kellermann
5823e79fe7 output/wasapi: remove broken Drain() implementation
The current Drain() implementation does what Cancel() should do; it
does not wait for completion, but instead discards the buffer.
2021-03-08 21:41:34 +01:00
Max Kellermann
5f656dffda output/wasapi: implement Cancel() 2021-03-08 19:58:15 +01:00
Max Kellermann
34d4d9157a output/wasapi: add inline 2021-03-08 19:57:40 +01:00
Max Kellermann
22c329cdb4 output/wasapi: convert pointer to reference 2021-03-08 19:56:56 +01:00
Max Kellermann
980ef82216 output/wasapi: move SetEventHandle() call to thread constructor 2021-03-08 17:52:44 +01:00
Max Kellermann
84a06a72df output/wasapi: fix coding style 2021-03-08 17:52:43 +01:00
Max Kellermann
4833d0891d output/wasapi: eliminate kErrorId 2021-03-08 17:47:07 +01:00
Max Kellermann
cd53ca22c6 output/wasapi: remove unused function SafeTry() 2021-03-08 17:43:36 +01:00
Max Kellermann
4d9af9a81b test/run_{input,output,convert}: switch file descriptors to binary mode
Fixes those programs on Windows.
2021-03-08 17:27:23 +01:00
Max Kellermann
d61341c0e3 io/FileDescriptor: add method SetBinaryMode() 2021-03-08 17:25:36 +01:00
Max Kellermann
eff50b263a test/run_output: use class StaticFifoBuffer 2021-03-08 17:01:48 +01:00
Max Kellermann
2bebc79363 test/run_convert: use std::byte 2021-03-08 16:58:43 +01:00
Max Kellermann
e777fb4edb test/run_convert: pass FileDescriptor to RunConvert() 2021-03-08 16:51:57 +01:00
Max Kellermann
3fb25d4062 test/run_convert: move code to RunConvert() 2021-03-08 16:46:21 +01:00
Max Kellermann
e227596c20 test/run_output: pass FileDescriptor to run_output() 2021-03-08 16:44:15 +01:00
Max Kellermann
ec76583c33 win32/Com: add COINIT_DISABLE_OLE1DDE
MSDN documentation suggests always passing this flag to reduce
overhead for an "obsolete technology".
2021-03-08 14:03:33 +01:00
Max Kellermann
927f1e03a3 win32/Com: make COINIT_APARTMENTTHREADED the default constructor 2021-03-08 14:02:49 +01:00
Max Kellermann
f2c679cfec win32/Com: remove the unused COINIT_MULTITHREADED constructor 2021-03-08 14:02:49 +01:00
Max Kellermann
6a75c48dba win32/HResult: add MakeHResultError()
None of the current FormatHResultError() callers need the format string.
2021-03-08 13:46:36 +01:00
Max Kellermann
48bdd09f64 win32/ComWorker: fold class COMWorkerThread into class COMWorker 2021-03-07 18:22:59 +01:00
Max Kellermann
cf108c389f win32/ComWorker: remove parameter passing from Async()
Parameters should better be captured.  This removes some complexity
from Async().
2021-03-07 18:20:59 +01:00
Max Kellermann
90d97053a8 win32/ComWorker: make COMWorker a real class, no static members 2021-03-06 20:46:29 +01:00
Shen-Ta Hsieh
e1fe9ebcd6 output/wasapi: Add dop support for WASAPI
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1102
2021-03-05 19:40:32 +01:00
Max Kellermann
93016ac6ab output/wasapi: check AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED
Stop early, don't try more formats if it is clear that we have no
chance.
2021-03-05 19:33:38 +01:00
Max Kellermann
fc20a1f10a output/wasapi: EnumerateDevices() logs, no std::vector 2021-03-05 19:27:52 +01:00
Max Kellermann
a4257e51d5 output/wasapi: reimplement SearchDevice() without EnumerateDevices() 2021-03-05 19:25:42 +01:00
Max Kellermann
2f2b3f1cdc output/wasapi: SearchDevice() returns IMMDevice 2021-03-05 19:25:42 +01:00
Max Kellermann
2ff6a9ad2b output/wasapi: GetDevice() returns IMMDevice 2021-03-05 19:25:42 +01:00
Max Kellermann
17d4873b60 output/wasapi: use default device only if none was configured 2021-03-05 19:25:42 +01:00
Max Kellermann
8b41c4f384 output/wasapi: release the COMWorker if OpenDevice() fails
Fixes assertion failure in the Thread destructor.
2021-03-05 19:25:42 +01:00
Max Kellermann
17f7098e27 output/wasapi: SafeTry() catches all exceptions
Fixes crash due to std::stoul() throwing std::invalid_argument.
2021-03-05 19:12:22 +01:00
Max Kellermann
9ff790b7bb output/wasapi: move COM utilities to separate headers 2021-03-05 18:33:31 +01:00
Max Kellermann
ebc1fe2821 win32/ComPtr: operator*() returns reference 2021-03-05 17:39:48 +01:00
Max Kellermann
bc2988144e test/run_output: use OptionParser, add option "--verbose" 2021-03-05 17:39:48 +01:00
Max Kellermann
b1a9958c66 test/run_output: add struct CommandLine 2021-03-05 17:39:48 +01:00
Max Kellermann
e6a81bb95c output/wasapi: split the header
Reduce header dependencies.
2021-03-05 16:43:44 +01:00
Max Kellermann
9521c1ad58 output/wasapi: use forward declarations in the header 2021-03-05 16:42:38 +01:00
Max Kellermann
6d65cc48d7 output/wasapi: use [[gnu::pure]] 2021-03-05 16:42:15 +01:00
Max Kellermann
681956a963 output/wasapi: include cleanup 2021-03-05 16:42:14 +01:00
Max Kellermann
052f64d648 output/wasapi: include config.h for ENABLE_DSD 2021-03-05 16:35:21 +01:00
Max Kellermann
afe621c25c output/wasapi: move to separate directory 2021-03-05 16:28:36 +01:00
Max Kellermann
637cf8a039 win32/WinEvent: add default value to Wait() 2021-03-05 16:05:32 +01:00
Max Kellermann
2011a6e2ee win32/WinEvent: un-inline the constructor
Reduce header dependencies.
2021-03-05 16:01:23 +01:00
Max Kellermann
d54830de12 thread/WindowsFuture: include cleanup 2021-03-05 13:50:16 +01:00
Max Kellermann
a7e7312cca win32/HResult: un-inline HResultCategory::message() 2021-03-05 13:40:40 +01:00
Max Kellermann
6b83fc6b57 win32/HResult: un-inline FormatHResultError()
Reduce header dependencies.
2021-03-05 13:40:38 +01:00
Max Kellermann
74f9e07151 win32/HResult: include cleanup 2021-03-05 13:40:37 +01:00
Max Kellermann
82a61ab3be win32/meson.build: fix syntax error 2021-03-05 13:40:34 +01:00
Max Kellermann
54c1794cee win32: build static library
Fixes linker failure on test/run_output.exe
2021-03-05 13:32:58 +01:00
Max Kellermann
c962a6be76 test/run_convert: fix Windows compiler errors 2021-03-05 13:24:28 +01:00
Max Kellermann
922c4bf3f0 test/TestLookupFile: fix Windows compiler errors 2021-03-05 13:22:34 +01:00
Max Kellermann
932756efce win32/ComWorker: fix the FormatHResultError() return type
Casting to std::runtime_error loses information (and prevents RVO).
2021-03-05 13:17:40 +01:00
Max Kellermann
7838265482 win32/ComWorker: remove debug log messages 2021-03-05 13:16:09 +01:00
Max Kellermann
b14b0e5634 win32/ComWorker: reorder includes 2021-03-05 13:15:45 +01:00
Max Kellermann
4d2d0e7bb8 win32/ComWorker: include cleanup 2021-03-05 13:15:21 +01:00
Rosen Penev
44378b7dbe use structured binding declarations
Shorter.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-03-04 20:28:02 +01:00
Shen-Ta Hsieh
da642b2890 src/output: add algorithm for finding usable AudioFormat
* Use PcmExport for 24bit packed output
2021-03-04 18:53:58 +01:00
Shen-Ta Hsieh
6f77af20d0 src/output: Set fallback setting for DSD 2021-03-04 18:50:56 +01:00
Shen-Ta Hsieh
010f65a1d6 src/output: Add Interrupt interface 2021-03-04 18:50:09 +01:00
Shen-Ta Hsieh
c46f97454a src/output: Reopen device on error 2021-03-04 18:49:28 +01:00
Shen-Ta Hsieh
844dbd2ec5 src/output: Use WinEvent for as a condition_variable without lock 2021-03-04 18:46:26 +01:00
Shen-Ta Hsieh
db7caa2dac src/output: Move event and spsc_queue into thread object 2021-03-04 18:45:56 +01:00
Shen-Ta Hsieh
2974737746 src/win32: Add ComWorker to run all COM function on same thread 2021-03-04 18:43:43 +01:00
Shen-Ta Hsieh
b1d7567226 win32: Add ComWorker to run all COM function on same thread 2021-03-04 18:43:16 +01:00
Max Kellermann
5103eb3039 meson.build: compile Win32Main.cxx only on Windows 2021-03-04 18:43:00 +01:00
Shen-Ta Hsieh
0cccdcf9b2 src/win32: Add support for COINIT_APARTMENTTHREADED 2021-03-04 18:37:56 +01:00
Shen-Ta Hsieh
22b840c2f1 win32/Com: use if with init-statement 2021-03-04 18:37:35 +01:00
Shen-Ta Hsieh
ed1a995bff thread: Add Future implement for mingw32 without pthread 2021-03-04 18:26:46 +01:00
Shen-Ta Hsieh
0f39dc1edb output/wasapi: use AUDCLNT_BUFFERFLAGS_SILENT for paused output 2021-03-04 18:17:57 +01:00
Max Kellermann
dc9103befe util/AllocatedString: remove Null(), IsNull() 2021-03-04 18:05:29 +01:00
Max Kellermann
67760f5283 util/AllocatedString: support casting a nulled instance to string_view 2021-03-04 18:05:29 +01:00
Max Kellermann
99405a4c93 util/AllocatedString: add operator=() 2021-03-04 18:05:26 +01:00
Max Kellermann
b833c5d2c7 util/AllocatedString: replace Clone() with copy constructor 2021-03-04 18:04:21 +01:00
Max Kellermann
bca5d79f88 util/AllocatedString: add const_pointer constructor 2021-03-04 18:04:17 +01:00
Max Kellermann
6e1c8edf09 util/AllocatedString: add string_view constructor
Replaces the static Duplicate() method.
2021-03-04 18:04:11 +01:00
Max Kellermann
32b7b2e2fa util/AllocatedString: add default constructor 2021-03-04 18:04:06 +01:00
Max Kellermann
cfb7f8ab84 util/AllocatedString: rename to BasicAllocatedString
To make things simpler, AllocatedString is now a non-template class.
2021-03-04 18:03:56 +01:00
Érico Rolim
8d80280ab9 time/ISO8601: don't use glibc extension in strptime.
Per the manual for strptime, %F is equivalent %Y-%m-%d, so use that
directly.
2021-03-04 17:49:51 +01:00
Érico Rolim
c95e3dc065 storage/plugins/CurlStorage: don't use glibc extension in
ParseTimePoint.

%Z is a glibc extension to strptime, and is a no-op there, due to the
mapping between timezone names and their definition (especially when the
name comes from a different machine) being ambiguous / impossible.  Time
in HTTP headers is guaranteed to be UTC.

Passing an unknown format to strptime() implementations that don't
support it will generally cause them to return NULL, which will lead to
ParseTimePoint throwing an exception and ParseTimeStamp using an
unnecessary fallback.

Since the timezone name goes at the end of the string, we don't need to
use %Z to skip it (could be an issue in a different time stamp format),
so simply removing %Z works best.
2021-03-04 17:48:23 +01:00
Max Kellermann
00a520a4c3 doc/user.rst: update Windows&Android build dependencies
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1112
2021-02-26 00:59:10 +01:00
Max Kellermann
6eba621045 decoder/ffmpeg: fix build problem with FFmpeg 3.4
Regression by commit a22d1c88d7

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1097
2021-02-22 13:36:46 +01:00
Max Kellermann
a9ad8fa505 decoder/ffmpeg: move code to IsSeekable(AVFormatContext) 2021-02-22 13:33:25 +01:00
Max Kellermann
85427826aa increment version number to 0.22.7 2021-02-17 14:36:06 +01:00
Florian Schlichting
25e0a90402 fix typo in protocol.rst
Exmaple -> Example
2021-02-17 03:18:23 +01:00
Max Kellermann
938728820b release v0.22.6 2021-02-16 13:56:14 +01:00
Max Kellermann
80531ef8d8 db/simple: fix ExportedSong move constructor for non-owning sources
If the constructor moves from an ExportedSong instance which refers to
somebody else's "Tag" instance, the newly constructed instance will
instead refer to its own empty "tag_buffer" field.  This broke
SimpleDatabase::GetSong(), i.e. all songs on the queue restored from
the state file or added using the "addid" command.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1089
2021-02-16 13:52:25 +01:00
Max Kellermann
a91fba6a3d increment version number to 0.22.6 2021-02-16 13:47:33 +01:00
Max Kellermann
f8be403c34 release v0.22.5 2021-02-15 21:18:18 +01:00
Max Kellermann
28a5cdf319 android/meson.build: update the SDK platform to 29
Needed for `requestLegacyExternalStorage` (commit ca02fb7782).
2021-02-15 21:17:26 +01:00
Max Kellermann
6b1d264b35 command/queue: better error message for open-ended range with "move"
The "move" command doesn't allow open-ended ranges because they don't
make a lot of sense; moving an open-ended range is only possible if
the destination index is before the range, and in that case, the
client should be well aware how many songs there are.

Closes https://github.com/MusicPlayerDaemon/MPD/pull/1057
2021-02-15 20:57:22 +01:00
Max Kellermann
a6c10e9a1c protocol/ArgParser: check for invalid ranges
Catch errors like that early, before invalid ranges get passed to
internal MPD subsystems.
2021-02-15 20:55:30 +01:00
Max Kellermann
19a46064e9 protocol/RangeArg: add methods IsWellFormed(), IsEmpty(), HasAtLeast(), Count() 2021-02-15 20:54:51 +01:00
Max Kellermann
b57eeaa720 protocol/RangeArg: add static method Single() 2021-02-15 20:29:37 +01:00
Max Kellermann
ad059d5804 protocol/RangeArg: add method IsOpenEnded() 2021-02-15 20:29:35 +01:00
Max Kellermann
6e1940e930 protocol/RangeArg: add static method OpenEnded() 2021-02-15 20:29:34 +01:00
Max Kellermann
103194e32d protocol/RangeArg: add missing noexcept 2021-02-15 19:56:02 +01:00
Shen-Ta Hsieh
481c330c17 src/output: Set thread name for Wasapi output thread 2021-02-15 17:51:49 +01:00
Shen-Ta Hsieh
7ef489e057 src/win32: run clang-format 2021-02-15 17:50:51 +01:00
Shen-Ta Hsieh
d9e5d5ff5b src/win32: Add error message for NO_ERROR 2021-02-15 17:45:25 +01:00
Max Kellermann
ca02fb7782 android/AndroidManifest.xml: enable requestLegacyExternalStorage
This is a workaround for the new scoped storage design in Android 11:

 https://developer.android.com/about/versions/11/privacy/storage

This needs a proper solution eventually, but this quick fix will do
until we change "targetSdkVersion" to 30.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1061
2021-02-15 17:43:05 +01:00
Max Kellermann
d4d06da2f8 db/simple: fix dangling LightSong::tag reference in moved ExportedSong
After commit 1afa33c3c7, an old bug was revealed:
SimpleDatabase::GetSong() constructs an ExportedSong instance by
moving the return value of Song::Export(), which causes the
LightSong::tag field to be dangling on the moved-from
ExportedSong::tag_buffer.  This broke tags from CUE sheets.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1070
2021-02-15 17:38:37 +01:00
Max Kellermann
efde78db77 output/Thread: skip drain calls if there is no data to be played
Keep track of whether there is data being played, and don't call
AudioOutput::Drain() after Cancel() has been called already.
2021-02-15 16:39:13 +01:00
Max Kellermann
f1b8bcd6b2 output/pulse: don't drain if stream is suspended or corked
In this state, we can't make any progress.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1084
2021-02-15 16:07:16 +01:00
Max Kellermann
c2bc3704e1 output/pulse: move code to virtual method Drain()
Drain only if it was requested explicitly.
2021-02-15 15:59:54 +01:00
Max Kellermann
def120aca4 output/pulse: eliminate the pause field
It is useless, because we're always checking pa_stream_is_corked().
2021-02-15 15:59:46 +01:00
Max Kellermann
6d2b09ac2b doc/developer.rst: update branch names 2021-02-15 13:41:46 +01:00
Max Kellermann
78b43a9930 doc/protocol.rst: document add on local socket
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1022
2021-02-15 13:00:18 +01:00
Max Kellermann
da5ff779c6 python/build/libs.py: enable CURL/schannel support on Windows
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1031
2021-02-07 21:58:08 +01:00
Max Kellermann
e7da5b104d archive/iso9660: another fix for unaligned reads
Commit 79b2366387 added the field `skip`
to support unaligned reads, but set the `offset` field to a wrong
value.  This resulted in miscalculation of `remaining`, causing
an assertion failure.

The fix is to assign `offset` the correct value, but consider the
`skip` value in the assertion.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1067
2021-02-07 21:41:51 +01:00
Max Kellermann
4be76f3c8f archive/iso9660: check "skip==0" before doing optimized large read
After a Seek() to an odd offset, some data needs to be skipped from
the start of the block, and reading right into the given buffer
doesn't work.
2021-02-07 21:38:13 +01:00
Max Kellermann
c58c53293c test/run_input: add option --seek 2021-02-07 21:20:17 +01:00
Max Kellermann
8695a2806a test/run_input: document more options 2021-02-07 21:17:10 +01:00
vkostas
a59f1b21a6 Fix: Separate Conductor from Performer
Conductor was incorrectly saved to Performer tag in MPD database
2021-02-07 20:45:01 +01:00
Max Kellermann
9e2d09dabc net/SocketError: add syscall specific check functions
Fixes Windows compatibility.
2021-01-21 22:05:21 +01:00
Max Kellermann
2719f62feb net/SocketError: relicense to BSD-2 2021-01-21 21:31:02 +01:00
Max Kellermann
234cedd6c6 increment version number to 0.22.5 2021-01-21 17:43:25 +01:00
Max Kellermann
5b946e9d95 android/AndroidManifest.xml: android release 0.22.4 2021-01-21 17:36:00 +01:00
Max Kellermann
b46ca50dcc android/AndroidManifest.xml: raise targetSdkVersion to 29
The Google overlords require me to change to 29 or else I can't upload
new releases to Google Play.

 https://developer.android.com/distribute/best-practices/develop/target-sdk
2021-01-21 17:35:59 +01:00
148 changed files with 3788 additions and 1501 deletions
.travis.ymlNEWSREADME.md
android
doc
meson.build
python/build
src
CommandLine.cxxMain.cxxRemoteTagCache.cxxTagAny.cxx
archive
client
command
db
decoder
event
fs
input
io
lib
mixer
neighbor
net
output
protocol
queue
song
sticker
storage
system
tag
thread
time
util
win32
zeroconf
subprojects
test
win32

@@ -2,70 +2,37 @@ language: cpp
jobs:
include:
# Ubuntu Bionic (18.04) with GCC 7
# Ubuntu Focal (20.04) with GCC 9.3
- os: linux
dist: bionic
dist: focal
addons:
apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- meson
- libgtest-dev
- libboost-dev
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Bionic (18.04) with GCC 7 on big-endian
# Ubuntu Focal (20.04) with GCC 9.3 on big-endian
- os: linux
arch: s390x
dist: bionic
dist: focal
addons:
apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- meson
- libgtest-dev
- libboost-dev
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Bionic (18.04) with GCC 7 on ARM64
# Ubuntu Focal (20.04) with GCC 9.3 on ARM64
- os: linux
arch: arm64
dist: bionic
dist: focal
addons:
apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- meson
- libgtest-dev
- libboost-dev
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Trusty (16.04) with GCC 8
- os: linux
@@ -75,7 +42,7 @@ jobs:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:mhier/libboost-latest'
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
- sourceline: 'ppa:ricotz/toolchain'
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- g++-8
@@ -94,12 +61,13 @@ jobs:
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
- os: osx
osx_image: xcode10.3
osx_image: xcode11.6
addons:
homebrew:
packages:
- ccache
- meson
- googletest
- icu4c
- ffmpeg
- libnfs
@@ -117,7 +85,6 @@ jobs:
- faad2
- wavpack
- libmpdclient
update: true
env:
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
@@ -134,13 +101,6 @@ before_install:
- eval "${MATRIX_EVAL}"
install:
# C++14
# Work around "Target /usr/local/lib/libgtest.a is a symlink
# belonging to nss. You can unlink it" during gtest install
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
- test "$TRAVIS_OS_NAME" != "osx" || brew install https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
before_script:
- ccache -s

58
NEWS

@@ -1,3 +1,61 @@
ver 0.22.9 (2021/06/23)
* database
- simple: load all .mpdignore files of all parent directories
* tags
- fix "readcomments" and "readpicture" on remote files with ID3 tags
* decoder
- ffmpeg: support the tags "sort_album", "album-sort", "artist-sort"
- ffmpeg: fix build failure with FFmpeg 3.4
* Android
- fix auto-start on boot in Android 8 or later
* Windows
- fix build failure with SQLite
ver 0.22.8 (2021/05/22)
* fix crash bug in "albumart" command (0.22.7 regression)
ver 0.22.7 (2021/05/19)
* protocol
- don't use glibc extension to parse time stamps
- optimize the "albumart" command
* input
- curl: send user/password in the first request, save one roundtrip
* decoder
- ffmpeg: fix build problem with FFmpeg 3.4
- gme: support RSN files
* storage
- curl: don't use glibc extension
* database
- simple: fix database corruption bug
* output
- fix crash when pausing with multiple partitions
- jack: enable on Windows
- httpd: send header "Access-Control-Allow-Origin: *"
- wasapi: add algorithm for finding usable audio format
- wasapi: use default device only if none was configured
- wasapi: add DoP support
ver 0.22.6 (2021/02/16)
* fix missing tags on songs in queue
ver 0.22.5 (2021/02/15)
* protocol
- error for malformed ranges instead of ignoring silently
- better error message for open-ended range with "move"
* database
- simple: fix missing CUE sheet metadata in "addid" command
* tags
- id: translate TPE3 to Conductor, not Performer
* archive
- iso9660: another fix for unaligned reads
* output
- httpd: error handling on Windows improved
- pulse: fix deadlock with "always_on"
* Windows:
- enable https:// support (via Schannel)
* Android
- work around "Permission denied" on mpd.conf
ver 0.22.4 (2021/01/21)
* protocol
- add command "binarylimit" to allow larger chunk sizes

@@ -14,7 +14,7 @@ For basic installation instructions
- [Manual](http://www.musicpd.org/doc/user/)
- [Forum](http://forum.musicpd.org/)
- [IRC](irc://chat.freenode.net/#mpd)
- [IRC](ircs://irc.libera.chat:6697/#mpd)
- [Bug tracker](https://github.com/MusicPlayerDaemon/MPD/issues/)
# Developers

@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="51"
android:versionName="0.22.1">
android:versionCode="57"
android:versionName="0.22.9">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
<uses-feature android:name="android.software.leanback"
android:required="false" />
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true"
android:requestLegacyExternalStorage="true"
android:icon="@drawable/icon"
android:banner="@drawable/icon"
android:label="@string/app_name">

@@ -5,8 +5,8 @@ android_ndk = get_option('android_ndk')
android_sdk = get_option('android_sdk')
android_abi = get_option('android_abi')
android_sdk_build_tools_version = '27.0.0'
android_sdk_platform = 'android-23'
android_sdk_build_tools_version = '29.0.3'
android_sdk_platform = 'android-29'
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)

@@ -414,6 +414,15 @@ public class Main extends Service implements Runnable {
* start Main service without any callback
*/
public static void start(Context context, boolean wakelock) {
context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock));
Intent intent = new Intent(context, Main.class)
.putExtra("wakelock", wakelock);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
/* in Android 8+, we need to use this method
or else we'll get "IllegalStateException:
app is in background" */
context.startForegroundService(intent);
else
context.startService(intent);
}
}

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

@@ -68,11 +68,11 @@ There are two active branches in the git repository:
- the "unstable" branch called ``master`` where new features are
merged. This will become the next major release eventually.
- the "stable" branch (currently called ``v0.21.x``) where only bug
- the "stable" branch (currently called ``v0.22.x``) where only bug
fixes are merged.
Once :program:`MPD` 0.22 is released, a new branch called ``v0.22.x``
will be created for 0.22 bug-fix releases; after that, ``v0.21.x``
Once :program:`MPD` 0.23 is released, a new branch called ``v0.23.x``
will be created for 0.23 bug-fix releases; after that, ``v0.22.x``
will eventually cease to be maintained.
After bug fixes have been added to the "stable" branch, it will be

@@ -22,20 +22,6 @@ if get_option('html_manual')
install: true,
install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()),
)
custom_target(
'upload',
input: sphinx_output,
output: 'upload',
build_always_stale: true,
command: [
'rsync', '-vpruz', '--delete', meson.current_build_dir() + '/',
'www.musicpd.org:/var/www/mpd/doc/',
'--chmod=Dug+rwx,Do+rx,Fug+rw,Fo+r',
'--include=html', '--include=html/**',
'--exclude=*',
],
)
endif
if get_option('manpages')

@@ -715,7 +715,7 @@ A resampler using `libsamplerate <http://www.mega-nerd.com/SRC/>`_ a.k.a. Secret
* - Name
- Description
* - **type**
- The interpolator type. See below for a list of known types.
- The interpolator type. Defaults to :samp:`2`. See below for a list of known types.
The following converter types are provided by libsamplerate:
@@ -910,6 +910,10 @@ jack
The jack plugin connects to a `JACK server <http://jackaudio.org/>`_.
On Windows, this plugin loads :file:`libjack64.dll` at runtime. This
means you need to `download and install the JACK windows build
<https://jackaudio.org/downloads/>`_.
.. list-table::
:widths: 20 80
:header-rows: 1
@@ -1171,6 +1175,8 @@ The `Windows Audio Session API <https://docs.microsoft.com/en-us/windows/win32/c
- Enumerate all devices in log while playing started. Useful for device configuration. The default value is "no".
* - **exclusive yes|no**
- Exclusive mode blocks all other audio source, and get best audio quality without resampling. Stopping playing release the exclusive control of the output device. The default value is "no".
* - **dop yes|no**
- Enable DSD over PCM. Require exclusive mode. The default value is "no".
.. _filter_plugins:

@@ -677,6 +677,11 @@ Whenever possible, ids should be used.
(directories add recursively). ``URI``
can also be a single file.
Clients that are connected via local socket may add arbitrary
local files (URI is an absolute path). Example::
add "/home/foo/Music/bar.ogg"
.. _command_addid:
:command:`addid {URI} [POSITION]`

@@ -55,7 +55,7 @@ and unpack it (or `clone the git repository
In any case, you need:
* a C++17 compiler (e.g. GCC 8 or clang 5)
* a C++17 compiler (e.g. GCC 8 or clang 7)
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* Boost 1.58
@@ -141,6 +141,15 @@ Basically, there are two ways to compile :program:`MPD` for Windows:
This section is about the latter.
You need:
* `mingw-w64 <http://mingw-w64.org/doku.php>`__
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* cmake
* pkg-config
* quilt
Just like with the native build, unpack the :program:`MPD` source
tarball and change into the directory. Then, instead of
:program:`meson`, type:
@@ -168,6 +177,11 @@ You need:
* Android SDK
* `Android NDK r22 <https://developer.android.com/ndk/downloads>`_
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* cmake
* pkg-config
* quilt
Just like with the native build, unpack the :program:`MPD` source
tarball and change into the directory. Then, instead of
@@ -674,6 +688,8 @@ The State File
- Specify the state file location. The parent directory must be writable by the :program:`MPD` user (+wx).
* - **state_file_interval SECONDS**
- Auto-save the state file this number of seconds after each state change. Defaults to 120 (2 minutes).
* - **restore_paused yes|no**
- If set to :samp:`yes`, then :program:`MPD` is put into pause mode instead of starting playback after startup. Default is :samp:`no`.
The Sticker Database
^^^^^^^^^^^^^^^^^^^^
@@ -1106,7 +1122,7 @@ Support
Getting Help
^^^^^^^^^^^^
The :program:`MPD` project runs a `forum <https://forum.musicpd.org/>`_ and an IRC channel (#mpd on Freenode) for requesting help. Visit the MPD help page for details on how to get help.
The :program:`MPD` project runs a `forum <https://forum.musicpd.org/>`_ and an IRC channel (#mpd on Libera.Chat) for requesting help. Visit the MPD help page for details on how to get help.
Common Problems
^^^^^^^^^^^^^^^

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.22.4',
version: '0.22.9',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c11',
@@ -24,8 +24,8 @@ c_compiler = meson.get_compiler('c')
if compiler.get_id() == 'gcc' and compiler.version().version_compare('<8')
warning('Your GCC version is too old. You need at least version 8.')
elif compiler.get_id() == 'clang' and compiler.version().version_compare('<5')
warning('Your clang version is too old. You need at least version 5.')
elif compiler.get_id() == 'clang' and compiler.version().version_compare('<7')
warning('Your clang version is too old. You need at least version 7.')
endif
version_conf = configuration_data()
@@ -42,57 +42,64 @@ common_cppflags = [
'-D_GNU_SOURCE',
]
common_cflags = [
]
common_cxxflags = [
test_global_common_flags = [
'-fvisibility=hidden',
]
test_common_flags = [
'-Wvla',
'-Wdouble-promotion',
'-fvisibility=hidden',
'-ffast-math',
'-ftree-vectorize',
]
test_global_cxxflags = test_global_common_flags + [
]
test_global_cflags = test_global_common_flags + [
]
test_cxxflags = test_common_flags + [
'-fno-threadsafe-statics',
'-fmerge-all-constants',
'-Wmissing-declarations',
'-Wshadow',
'-Wpointer-arith',
'-Wcast-qual',
'-Wwrite-strings',
'-Wsign-compare',
'-Wcomma',
'-Wcomma-subscript',
'-Wextra-semi',
'-Wmismatched-tags',
'-Wmissing-declarations',
'-Woverloaded-virtual',
'-Wshadow',
'-Wsign-promo',
'-Wunused',
'-Wvolatile',
'-Wvirtual-inheritance',
'-Wwrite-strings',
# a vtable without a dtor is just fine
'-Wno-non-virtual-dtor',
# clang specific warning options:
'-Wcomma',
'-Wheader-hygiene',
'-Winconsistent-missing-destructor-override',
'-Wunreachable-code-break',
'-Wunused',
'-Wunreachable-code-aggressive',
'-Wused-but-marked-unused',
'-Wno-non-virtual-dtor',
]
if compiler.get_id() == 'clang'
# Workaround for clang bug
# https://bugs.llvm.org/show_bug.cgi?id=32611
test_cxxflags += '-funwind-tables'
if compiler.get_id() != 'gcc' or compiler.version().version_compare('>=9')
# The GCC 8 implementation of this flag is buggy: it complains even
# if "final" is present, which implies "override".
test_cxxflags += '-Wsuggest-override'
endif
test_cflags = test_common_flags + [
'-Wcast-qual',
'-Wmissing-prototypes',
'-Wshadow',
'-Wpointer-arith',
'-Wstrict-prototypes',
'-Wcast-qual',
'-Wwrite-strings',
'-pedantic',
]
test_ldflags = [
@@ -104,11 +111,11 @@ test_ldflags = [
]
if get_option('buildtype') != 'debug'
test_cxxflags += [
test_global_cxxflags += [
'-ffunction-sections',
'-fdata-sections',
]
test_cflags += [
test_global_cflags += [
'-ffunction-sections',
'-fdata-sections',
]
@@ -118,15 +125,20 @@ if get_option('buildtype') != 'debug'
endif
if get_option('fuzzer')
fuzzer_flags = ['-fsanitize=fuzzer,address,undefined']
fuzzer_flags = ['-fsanitize=fuzzer']
if get_option('b_sanitize') == 'none'
fuzzer_flags += ['-fsanitize=address,undefined']
endif
add_global_arguments(fuzzer_flags, language: 'cpp')
add_global_arguments(fuzzer_flags, language: 'c')
add_global_link_arguments(fuzzer_flags, language: 'cpp')
endif
add_global_arguments(common_cxxflags + compiler.get_supported_arguments(test_cxxflags), language: 'cpp')
add_global_arguments(common_cflags + c_compiler.get_supported_arguments(test_cflags), language: 'c')
add_global_link_arguments(compiler.get_supported_link_arguments(test_ldflags), language: 'cpp')
add_global_arguments(compiler.get_supported_arguments(test_global_cxxflags), language: 'cpp')
add_global_arguments(c_compiler.get_supported_arguments(test_global_cflags), language: 'c')
add_project_arguments(compiler.get_supported_arguments(test_cxxflags), language: 'cpp')
add_project_arguments(c_compiler.get_supported_arguments(test_cflags), language: 'c')
add_project_link_arguments(compiler.get_supported_link_arguments(test_ldflags), language: 'cpp')
is_linux = host_machine.system() == 'linux'
is_android = get_option('android_ndk') != ''
@@ -140,10 +152,29 @@ endif
if is_windows
common_cppflags += [
'-DWIN32_LEAN_AND_MEAN',
# enable Windows Vista APIs
'-DWINVER=0x0600', '-D_WIN32_WINNT=0x0600',
'-DSTRICT',
# enable Unicode support (TCHAR=wchar_t) in the Windows API (macro
# "UNICODE) and the C library (macro "_UNICODE")
'-DUNICODE', '-D_UNICODE',
# enable strict type checking in the Windows API headers
'-DSTRICT',
# reduce header bloat by disabling obscure and obsolete Windows
# APIs
'-DWIN32_LEAN_AND_MEAN',
# disable more Windows APIs which are not used by MPD
'-DNOGDI', '-DNOBITMAP', '-DNOCOMM',
'-DNOUSER',
# reduce COM header bloat
'-DCOM_NO_WINDOWS_H',
# disable Internet Explorer specific APIs
'-D_WIN32_IE=0',
]
subdir('win32')
@@ -272,7 +303,6 @@ sources = [
'src/LogInit.cxx',
'src/ls.cxx',
'src/Instance.cxx',
'src/win32/Win32Main.cxx',
'src/MusicBuffer.cxx',
'src/MusicPipe.cxx',
'src/MusicChunk.cxx',
@@ -320,6 +350,12 @@ sources = [
'src/PlaylistFile.cxx',
]
if is_windows
sources += [
'src/win32/Win32Main.cxx',
]
endif
if not is_android
sources += [
'src/CommandLine.cxx',
@@ -354,6 +390,7 @@ subdir('src/system')
subdir('src/thread')
subdir('src/net')
subdir('src/event')
subdir('src/win32')
subdir('src/apple')

@@ -21,3 +21,8 @@ class BoostProject(Project):
dest = os.path.join(includedir, 'boost')
shutil.rmtree(dest, ignore_errors=True)
shutil.copytree(os.path.join(src, 'boost'), dest)
# touch the boost/version.hpp file to ensure it's newer than
# the downloaded Boost tarball, to avoid reinstalling Boost on
# every run
os.utime(os.path.join(toolchain.install_prefix, self.installed))

47
python/build/jack.py Normal file

@@ -0,0 +1,47 @@
import os, shutil
import re
from .project import Project
# This class installs just the public headers and a fake pkg-config
# file which defines the macro "DYNAMIC_JACK". This tells MPD's JACK
# output plugin to load the libjack64.dll dynamically using
# LoadLibrary(). This kludge avoids the runtime DLL dependency for
# users who don't use JACK, but still allows using the system JACK
# client library.
#
# The problem with JACK is that it uses an extremely fragile shared
# memory protocol to communicate with the daemon. One needs to use
# daemon and client library from the same build. That's why we don't
# build libjack statically here; it would probably not be compatible
# with the user's JACK daemon.
class JackProject(Project):
def __init__(self, url, md5, installed,
**kwargs):
m = re.match(r'.*/v([\d.]+)\.tar\.gz$', url)
self.version = m.group(1)
Project.__init__(self, url, md5, installed,
name='jack2', version=self.version,
base='jack2-' + self.version,
**kwargs)
def build(self, toolchain):
src = self.unpack(toolchain)
includes = ['jack.h', 'ringbuffer.h', 'systemdeps.h', 'transport.h', 'types.h', 'weakmacros.h']
includedir = os.path.join(toolchain.install_prefix, 'include', 'jack')
os.makedirs(includedir, exist_ok=True)
for i in includes:
shutil.copyfile(os.path.join(src, 'common', 'jack', i),
os.path.join(includedir, i))
with open(os.path.join(toolchain.install_prefix, 'lib', 'pkgconfig', 'jack.pc'), 'w') as f:
print("prefix=" + toolchain.install_prefix, file=f)
print("", file=f)
print("Name: jack", file=f)
print("Description: dummy", file=f)
print("Version: " + self.version, file=f)
print("Libs: ", file=f)
print("Cflags: -DDYNAMIC_JACK", file=f)

@@ -9,6 +9,7 @@ from build.autotools import AutotoolsProject
from build.ffmpeg import FfmpegProject
from build.openssl import OpenSSLProject
from build.boost import BoostProject
from build.jack import JackProject
libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz',
@@ -149,8 +150,8 @@ gme = CmakeProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.3.1.tar.xz',
'ad009240d46e307b4e03a213a0f49c11b650e445b1f8be0dda2a9212b34d2ffb',
'http://ffmpeg.org/releases/ffmpeg-4.4.tar.xz',
'06b10a183ce5371f915c6bb15b7b1fffbe046e8275099c96affc29e17645d909',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -378,14 +379,14 @@ ffmpeg = FfmpegProject(
)
openssl = OpenSSLProject(
'https://www.openssl.org/source/openssl-3.0.0-alpha10.tar.gz',
'b1699acf2148db31f12edf5ebfdf12a92bfd3f0e60538d169710408a3cd3b138',
'https://www.openssl.org/source/openssl-3.0.0-alpha16.tar.gz',
'08ce8244b59d75f40f91170dfcb012bf25309cdcb1fef9502e39d694f883d1d1',
'include/openssl/ossl_typ.h',
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.74.0.tar.xz',
'999d5f2c403cf6e25d58319fdd596611e455dd195208746bc6e6d197a77e878b',
'https://curl.se/download/curl-7.76.1.tar.xz',
'64bb5288c39f0840c07d077e30d9052e1cbb9fa6c2dc52523824cc859e679145',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -407,6 +408,9 @@ curl = AutotoolsProject(
'--disable-progress-meter',
'--disable-alt-svc',
'--without-gnutls', '--without-nss', '--without-libssh2',
# native Windows SSL/TLS support, option ignored on non-Windows builds
'--with-schannel',
],
patches='src/lib/curl/patches',
@@ -440,8 +444,14 @@ libnfs = AutotoolsProject(
autoreconf=True,
)
jack = JackProject(
'https://github.com/jackaudio/jack2/archive/v1.9.17.tar.gz',
'38f674bbc57852a8eb3d9faa1f96a0912d26f7d5df14c11005ad499c8ae352f2',
'lib/pkgconfig/jack.pc',
)
boost = BoostProject(
'https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.bz2',
'953db31e016db7bb207f11432bef7df100516eeb746843fa0486a222e3fd49cb',
'https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2',
'f0397ba6e982c4450f27bf32a2a83292aba035b827a5623a14636ea583318c41',
'include/boost/version.hpp',
)

@@ -20,7 +20,7 @@ class Project:
self.base = base
if name is None or version is None:
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-alpha\d+)?)$', self.base)
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-alpha\d+)?)(\+.*)?$', self.base)
if name is None: name = m.group(1)
if version is None: version = m.group(2)

@@ -113,7 +113,7 @@ static void version()
printf("Music Player Daemon " VERSION " (%s)"
"\n"
"Copyright 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
"Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>\n"
"Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
GIT_VERSION);

@@ -477,6 +477,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
#endif
ZeroconfInit(raw_config, instance.event_loop);
AtScopeExit() { ZeroconfDeinit(); };
#ifdef ENABLE_DATABASE
if (create_db) {
@@ -537,9 +538,6 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
instance.state_file->Write();
instance.BeginShutdownUpdate();
ZeroconfDeinit();
instance.BeginShutdownPartitions();
}

@@ -42,9 +42,9 @@ RemoteTagCache::Lookup(const std::string &uri) noexcept
std::unique_lock<Mutex> lock(mutex);
KeyMap::insert_commit_data hint;
auto result = map.insert_check(uri, Item::Hash(), Item::Equal(), hint);
if (result.second) {
auto *item = new Item(*this, uri);
auto [tag, value] = map.insert_check(uri, Item::Hash(), Item::Equal(), hint);
if (value) {
auto item = new Item(*this, uri);
map.insert_commit(*item, hint);
waiting_list.push_back(*item);
lock.unlock();
@@ -70,15 +70,13 @@ RemoteTagCache::Lookup(const std::string &uri) noexcept
ItemResolved(*item);
return;
}
} else if (result.first->scanner) {
} else if (tag->scanner) {
/* already scanning this one - no-op */
} else {
/* already finished: re-invoke the handler */
auto &item = *result.first;
idle_list.erase(waiting_list.iterator_to(item));
invoke_list.push_back(item);
idle_list.erase(waiting_list.iterator_to(*tag));
invoke_list.push_back(*tag);
ScheduleInvokeHandlers();
}

@@ -25,6 +25,7 @@
#include "client/Client.hxx"
#include "protocol/Ack.hxx"
#include "fs/AllocatedPath.hxx"
#include "input/InputStream.hxx"
#include "util/Compiler.h"
#include "util/UriExtract.hxx"
#include "LocateUri.hxx"
@@ -32,8 +33,13 @@
static void
TagScanStream(const char *uri, TagHandler &handler)
{
if (!tag_stream_scan(uri, handler))
Mutex mutex;
auto is = InputStream::OpenReady(uri, mutex);
if (!tag_stream_scan(*is, handler))
throw ProtocolError(ACK_ERROR_NO_EXIST, "Failed to load file");
ScanGenericTags(*is, handler);
}
static void

@@ -221,8 +221,8 @@ public:
if (new_offset > size)
throw std::runtime_error("Invalid seek offset");
offset = new_offset;
skip = new_offset % ISO_BLOCKSIZE;
offset = new_offset - skip;
buffer.Clear();
}
};
@@ -260,13 +260,13 @@ Iso9660InputStream::Read(std::unique_lock<Mutex> &,
if (r.empty()) {
/* the buffer is empty - read more data from the ISO file */
assert(offset % ISO_BLOCKSIZE == 0);
assert((offset - skip) % ISO_BLOCKSIZE == 0);
const ScopeUnlock unlock(mutex);
const lsn_t read_lsn = lsn + offset / ISO_BLOCKSIZE;
if (read_size >= ISO_BLOCKSIZE) {
if (read_size >= ISO_BLOCKSIZE && skip == 0) {
/* big read - read right into the caller's buffer */
auto nbytes = iso->SeekRead(ptr, read_lsn,

@@ -23,6 +23,7 @@
#include "Message.hxx"
#include "command/CommandResult.hxx"
#include "command/CommandListBuilder.hxx"
#include "input/LastInputStream.hxx"
#include "tag/Mask.hxx"
#include "event/FullyBufferedSocket.hxx"
#include "event/TimerEvent.hxx"
@@ -90,6 +91,13 @@ public:
*/
size_t binary_limit = 8192;
/**
* This caches the last "albumart" InputStream instance, to
* avoid repeating the search for each chunk requested by this
* client.
*/
LastInputStream last_album_art;
private:
static constexpr size_t MAX_SUBSCRIPTIONS = 16;

@@ -44,7 +44,8 @@ Client::Client(EventLoop &_loop, Partition &_partition,
partition(&_partition),
permission(_permission),
uid(_uid),
num(_num)
num(_num),
last_album_art(_loop)
{
timeout_event.Schedule(client_timeout);
}

@@ -61,7 +61,12 @@ Response::WriteBinary(ConstBuffer<void> payload) noexcept
{
assert(payload.size <= client.binary_limit);
return Format("binary: %zu\n", payload.size) &&
return
#ifdef _WIN32
Format("binary: %lu\n", (unsigned long)payload.size) &&
#else
Format("binary: %zu\n", payload.size) &&
#endif
Write(payload.data, payload.size) &&
Write("\n");
}

@@ -34,8 +34,7 @@ Client::Subscribe(const char *channel) noexcept
if (num_subscriptions >= MAX_SUBSCRIPTIONS)
return Client::SubscribeResult::FULL;
auto r = subscriptions.insert(channel);
if (!r.second)
if (!subscriptions.insert(channel).second)
return Client::SubscribeResult::ALREADY;
++num_subscriptions;

@@ -191,9 +191,16 @@ read_stream_art(Response &r, const char *uri, size_t offset)
{
const auto art_directory = PathTraitsUTF8::GetParent(uri);
Mutex mutex;
// TODO: eliminate this const_cast
auto &client = const_cast<Client &>(r.GetClient());
InputStreamPtr is = find_stream_art(art_directory, mutex);
/* to avoid repeating the search for each chunk request by the
same client, use the #LastInputStream class to cache the
#InputStream instance */
auto *is = client.last_album_art.Open(art_directory, [](std::string_view directory,
Mutex &mutex){
return find_stream_art(directory, mutex);
});
if (is == nullptr) {
r.Error(ACK_ERROR_NO_EXIST, "No file exists");
@@ -219,12 +226,17 @@ read_stream_art(Response &r, const char *uri, size_t offset)
std::size_t read_size = 0;
if (buffer_size > 0) {
std::unique_lock<Mutex> lock(mutex);
std::unique_lock<Mutex> lock(is->mutex);
is->Seek(lock, offset);
read_size = is->Read(lock, buffer.get(), buffer_size);
}
#ifdef _WIN32
r.Format("size: %lu\n", (unsigned long)art_file_size);
#else
r.Format("size: %" PRIoffset "\n", art_file_size);
#endif
r.WriteBinary({buffer.get(), read_size});
return CommandResult::OK;
@@ -306,7 +318,11 @@ public:
return;
}
#ifdef _WIN32
response.Format("size: %lu\n", (unsigned long)buffer.size);
#else
response.Format("size: %zu\n", buffer.size);
#endif
if (mime_type != nullptr)
response.Format("type: %s\n", mime_type);

@@ -92,7 +92,7 @@ handle_load(Client &client, Request args, [[maybe_unused]] Response &r)
auto &instance = client.GetInstance();
const unsigned new_size = playlist.GetLength();
for (unsigned i = old_size; i < new_size; ++i)
instance.LookupRemoteTag(playlist.queue.Get(i).GetURI());
instance.LookupRemoteTag(playlist.queue.Get(i).GetRealURI());
return CommandResult::OK;
}

@@ -326,6 +326,11 @@ CommandResult
handle_move(Client &client, Request args, [[maybe_unused]] Response &r)
{
RangeArg range = args.ParseRange(0);
if (range.IsOpenEnded()) {
r.Error(ACK_ERROR_ARG, "Open-ended range not supported");
return CommandResult::ERROR;
}
int to = args.ParseInt(1);
client.GetPartition().MoveRange(range.start, range.end, to);
return CommandResult::OK;

@@ -57,9 +57,9 @@ Print(Response &r, TagType group, const TagCountMap &m) noexcept
{
assert(unsigned(group) < TAG_NUM_OF_ITEM_TYPES);
for (const auto &i : m) {
tag_print(r, group, i.first.c_str());
PrintSearchStats(r, i.second);
for (const auto &[tag, stats] : m) {
tag_print(r, group, tag.c_str());
PrintSearchStats(r, stats);
}
}
@@ -68,8 +68,7 @@ stats_visitor_song(SearchStats &stats, const LightSong &song) noexcept
{
stats.n_songs++;
const auto duration = song.GetDuration();
if (!duration.IsNegative())
if (const auto duration = song.GetDuration(); !duration.IsNegative())
stats.total_duration += duration;
}
@@ -77,8 +76,7 @@ static void
CollectGroupCounts(TagCountMap &map, const Tag &tag,
const char *value) noexcept
{
auto r = map.insert(std::make_pair(value, SearchStats()));
SearchStats &s = r.first->second;
auto &s = map.insert(std::make_pair(value, SearchStats())).first->second;
++s.n_songs;
if (!tag.duration.IsNegative())
s.total_duration += tag.duration;

@@ -195,11 +195,11 @@ PrintUniqueTags(Response &r, ConstBuffer<TagType> tag_types,
const char *const name = tag_item_names[tag_types.front()];
tag_types.pop_front();
for (const auto &i : map) {
r.Format("%s: %s\n", name, i.first.c_str());
for (const auto &[key, tag] : map) {
r.Format("%s: %s\n", name, key.c_str());
if (!tag_types.empty())
PrintUniqueTags(r, tag_types, i.second);
PrintUniqueTags(r, tag_types, tag);
}
}

@@ -424,6 +424,7 @@ SendGroup(mpd_connection *connection, TagType group)
return mpd_search_add_group_tag(connection, tag);
#else
(void)connection;
(void)group;
throw std::runtime_error("Grouping requires libmpdclient 2.12");
#endif

@@ -138,13 +138,10 @@ Directory::LookupDirectory(std::string_view _uri) noexcept
Directory *d = this;
do {
auto s = uri.Split(PathTraitsUTF8::SEPARATOR);
if (s.first.empty())
auto [name, rest] = uri.Split(PathTraitsUTF8::SEPARATOR);
if (name.empty())
break;
const auto name = s.first;
const auto rest = s.second;
Directory *tmp = d->FindChild(name);
if (tmp == nullptr)
/* not found */

@@ -29,6 +29,12 @@
* a #LightSong, e.g. a merged #Tag.
*/
class ExportedSong : public LightSong {
/**
* A reference target for LightSong::tag, but it is only used
* if this instance "owns" the #Tag. For instances referring
* to a foreign #Tag instance (e.g. a Song::tag), this field
* is not used (and empty).
*/
Tag tag_buffer;
public:
@@ -37,6 +43,25 @@ public:
ExportedSong(const char *_uri, Tag &&_tag) noexcept
:LightSong(_uri, tag_buffer),
tag_buffer(std::move(_tag)) {}
/* this custom move constructor is necessary so LightSong::tag
points to this instance's #Tag field instead of leaving a
dangling reference to the source object's #Tag field */
ExportedSong(ExportedSong &&src) noexcept
:LightSong(src,
/* refer to tag_buffer only if the
moved-from instance also owned the Tag
which its LightSong::tag field refers
to */
OwnsTag() ? tag_buffer : src.tag),
tag_buffer(std::move(src.tag_buffer)) {}
ExportedSong &operator=(ExportedSong &&) = delete;
private:
bool OwnsTag() const noexcept {
return &tag == &tag_buffer;
}
};
#endif

@@ -312,6 +312,29 @@ UpdateWalk::SkipSymlink(const Directory *directory,
#endif
}
static void
LoadExcludeListOrThrow(const Storage &storage, const Directory &directory,
ExcludeList &exclude_list)
{
Mutex mutex;
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
".mpdignore")).c_str(),
mutex);
exclude_list.Load(std::move(is));
}
static void
LoadExcludeListOrLog(const Storage &storage, const Directory &directory,
ExcludeList &exclude_list) noexcept
{
try {
LoadExcludeListOrThrow(storage, directory, exclude_list);
} catch (...) {
if (!IsFileNotFound(std::current_exception()))
LogError(std::current_exception());
}
}
bool
UpdateWalk::UpdateDirectory(Directory &directory,
const ExcludeList &exclude_list,
@@ -331,17 +354,7 @@ UpdateWalk::UpdateDirectory(Directory &directory,
}
ExcludeList child_exclude_list(exclude_list);
try {
Mutex mutex;
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
".mpdignore")).c_str(),
mutex);
child_exclude_list.Load(std::move(is));
} catch (...) {
if (!IsFileNotFound(std::current_exception()))
LogError(std::current_exception());
}
LoadExcludeListOrLog(storage, directory, child_exclude_list);
if (!child_exclude_list.IsEmpty())
RemoveExcludedFromDirectory(directory, child_exclude_list);
@@ -427,26 +440,46 @@ UpdateWalk::DirectoryMakeUriParentChecked(Directory &root,
StringView uri(_uri);
while (true) {
auto s = uri.Split('/');
const std::string_view name = s.first;
const auto rest = s.second;
auto [name, rest] = uri.Split('/');
if (rest == nullptr)
break;
if (!name.empty()) {
directory = DirectoryMakeChildChecked(*directory,
std::string(name).c_str(),
s.first);
name);
if (directory == nullptr)
break;
}
uri = s.second;
uri = rest;
}
return directory;
}
static void
LoadExcludeLists(std::forward_list<ExcludeList> &lists,
const Storage &storage, const Directory &directory) noexcept
{
assert(!lists.empty());
if (!directory.IsRoot())
LoadExcludeLists(lists, storage, *directory.parent);
lists.emplace_front();
LoadExcludeListOrLog(storage, directory, lists.front());
}
static auto
LoadExcludeLists(const Storage &storage, const Directory &directory) noexcept
{
std::forward_list<ExcludeList> lists;
lists.emplace_front();
LoadExcludeLists(lists, storage, directory);
return lists;
}
inline void
UpdateWalk::UpdateUri(Directory &root, const char *uri) noexcept
try {
@@ -467,9 +500,8 @@ try {
return;
}
ExcludeList exclude_list;
UpdateDirectoryChild(*parent, exclude_list, name, info);
const auto exclude_lists = LoadExcludeLists(storage, *parent);
UpdateDirectoryChild(*parent, exclude_lists.front(), name, info);
} catch (...) {
LogError(std::current_exception());
}

@@ -464,6 +464,17 @@ FfmpegCheckTag(DecoderClient &client, InputStream *is,
client.SubmitTag(is, tag.Commit());
}
static bool
IsSeekable(const AVFormatContext &format_context) noexcept
{
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100)
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0;
#else
(void)format_context;
return false;
#endif
}
static void
FfmpegDecode(DecoderClient &client, InputStream *input,
AVFormatContext &format_context)
@@ -521,7 +532,7 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
client.Ready(audio_format,
input
? input->IsSeekable()
: (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0,
: IsSeekable(format_context),
total_time);
FfmpegParseMetaData(client, format_context, audio_stream);
@@ -648,6 +659,8 @@ ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
return FfmpegScanStream(*f, handler);
}
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
static void
ffmpeg_uri_decode(DecoderClient &client, const char *uri)
{
@@ -679,6 +692,8 @@ ffmpeg_protocols() noexcept
return protocols;
}
#endif
/**
* A list of extensions found for the formats supported by ffmpeg.
* This list is current as of 02-23-09; To find out if there are more
@@ -802,6 +817,8 @@ static const char *const ffmpeg_mime_types[] = {
constexpr DecoderPlugin ffmpeg_decoder_plugin =
DecoderPlugin("ffmpeg", ffmpeg_decode, ffmpeg_scan_stream)
.WithInit(ffmpeg_init, ffmpeg_finish)
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
.WithProtocols(ffmpeg_protocols, ffmpeg_uri_decode)
#endif
.WithSuffixes(ffmpeg_suffixes)
.WithMimeTypes(ffmpeg_mime_types);

@@ -30,11 +30,22 @@ extern "C" {
#include <libavutil/dict.h>
}
/**
* FFmpeg specific tag name mappings, as supported by
* libavformat/id3v2.c, libavformat/mov.c and others.
*/
static constexpr struct tag_table ffmpeg_tags[] = {
{ "year", TAG_DATE },
{ "author-sort", TAG_ARTIST_SORT },
/* from libavformat/id3v2.c, libavformat/mov.c */
{ "album_artist", TAG_ALBUM_ARTIST },
{ "album_artist-sort", TAG_ALBUM_ARTIST_SORT },
/* from libavformat/id3v2.c */
{ "album-sort", TAG_ALBUM_SORT },
{ "artist-sort", TAG_ARTIST_SORT },
/* from libavformat/mov.c */
{ "sort_album_artist", TAG_ALBUM_ARTIST_SORT },
{ "sort_album", TAG_ALBUM_SORT },
{ "sort_artist", TAG_ARTIST_SORT },
/* sentinel */
{ nullptr, TAG_NUM_OF_ITEM_TYPES }

@@ -344,7 +344,7 @@ gme_container_scan(Path path_fs)
static const char *const gme_suffixes[] = {
"ay", "gbs", "gym", "hes", "kss", "nsf",
"nsfe", "sap", "spc", "vgm", "vgz",
"nsfe", "rsn", "sap", "spc", "vgm", "vgz",
nullptr
};

@@ -889,8 +889,6 @@ inline bool
MadDecoder::HandleCurrentFrame() noexcept
{
switch (mute_frame) {
DecoderCommand cmd;
case MadDecoderMuteFrame::SKIP:
mute_frame = MadDecoderMuteFrame::NONE;
break;
@@ -899,8 +897,8 @@ MadDecoder::HandleCurrentFrame() noexcept
mute_frame = MadDecoderMuteFrame::NONE;
UpdateTimerNextFrame();
break;
case MadDecoderMuteFrame::NONE:
cmd = SynthAndSubmit();
case MadDecoderMuteFrame::NONE: {
const auto cmd = SynthAndSubmit();
UpdateTimerNextFrame();
if (cmd == DecoderCommand::SEEK) {
assert(input_stream.IsSeekable());
@@ -922,6 +920,7 @@ MadDecoder::HandleCurrentFrame() noexcept
} else if (cmd != DecoderCommand::NONE)
return false;
}
}
return true;
}

@@ -456,7 +456,7 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
} while (cmd != DecoderCommand::STOP);
}
static AllocatedString<char>
static AllocatedString
Windows1252ToUTF8(const char *s) noexcept
{
#ifdef HAVE_ICU_CONVERTER
@@ -469,9 +469,9 @@ Windows1252ToUTF8(const char *s) noexcept
* Fallback to not transcoding windows-1252 to utf-8, that may result
* in invalid utf-8 unless nonprintable characters are replaced.
*/
auto t = AllocatedString<char>::Duplicate(s);
AllocatedString t(s);
for (size_t i = 0; t[i] != AllocatedString<char>::SENTINEL; i++)
for (size_t i = 0; t[i] != AllocatedString::SENTINEL; i++)
if (!IsPrintableASCII(t[i]))
t[i] = '?';
@@ -479,7 +479,7 @@ Windows1252ToUTF8(const char *s) noexcept
}
gcc_pure
static AllocatedString<char>
static AllocatedString
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
{
#ifdef HAVE_SIDPLAYFP
@@ -496,7 +496,7 @@ GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
}
gcc_pure
static AllocatedString<char>
static AllocatedString
GetDateString(const SidTuneInfo &info) noexcept
{
/*
@@ -507,12 +507,12 @@ GetDateString(const SidTuneInfo &info) noexcept
* author or group> may be for example Rob Hubbard. A full field
* may be for example "1987 Rob Hubbard".
*/
AllocatedString<char> release = GetInfoString(info, 2);
AllocatedString release = GetInfoString(info, 2);
/* Keep the <year> part only for the date. */
for (size_t i = 0; release[i] != AllocatedString<char>::SENTINEL; i++)
for (size_t i = 0; release[i] != AllocatedString::SENTINEL; i++)
if (std::isspace(release[i])) {
release[i] = AllocatedString<char>::SENTINEL;
release[i] = AllocatedString::SENTINEL;
break;
}

@@ -36,7 +36,7 @@ BufferedSocket::DirectRead(void *data, size_t length) noexcept
}
const auto code = GetSocketError();
if (IsSocketErrorAgain(code))
if (IsSocketErrorReceiveWouldBlock(code))
return 0;
if (IsSocketErrorClosed(code))

@@ -31,7 +31,7 @@ FullyBufferedSocket::DirectWrite(const void *data, size_t length) noexcept
const auto nbytes = GetSocket().Write((const char *)data, length);
if (gcc_unlikely(nbytes < 0)) {
const auto code = GetSocketError();
if (IsSocketErrorAgain(code))
if (IsSocketErrorSendWouldBlock(code))
return 0;
IdleMonitor::Cancel();

@@ -24,7 +24,7 @@
#ifdef _WIN32
#include <windows.h>
#include <fileapi.h>
#include <tchar.h>
/**

36
src/fs/Glob.cxx Normal file

@@ -0,0 +1,36 @@
/*
* 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.
*/
#ifdef _WIN32
// COM needs the "MSG" typedef, and shlwapi.h includes COM headers
#undef NOUSER
#endif
#include "Glob.hxx"
#ifdef _WIN32
#include <shlwapi.h>
bool
Glob::Check(const char *name_fs) const noexcept
{
return PathMatchSpecA(name_fs, pattern.c_str());
}
#endif

@@ -24,45 +24,44 @@
#ifdef HAVE_FNMATCH
#define HAVE_CLASS_GLOB
#include <string>
#include <fnmatch.h>
#elif defined(_WIN32)
#define HAVE_CLASS_GLOB
#include <string>
#include <shlwapi.h>
#endif
#ifdef HAVE_CLASS_GLOB
#include "util/Compiler.h"
#include <string>
/**
* A pattern that matches file names. It may contain shell wildcards
* (asterisk and question mark).
*/
class Glob {
#if defined(HAVE_FNMATCH) || defined(_WIN32)
std::string pattern;
#endif
public:
#if defined(HAVE_FNMATCH) || defined(_WIN32)
explicit Glob(const char *_pattern)
:pattern(_pattern) {}
Glob(Glob &&other)
:pattern(std::move(other.pattern)) {}
#endif
Glob(Glob &&other) noexcept = default;
Glob &operator=(Glob &&other) noexcept = default;
gcc_pure
bool Check(const char *name_fs) const noexcept {
#ifdef HAVE_FNMATCH
return fnmatch(pattern.c_str(), name_fs, 0) == 0;
#elif defined(_WIN32)
return PathMatchSpecA(name_fs, pattern.c_str());
#endif
}
bool Check(const char *name_fs) const noexcept;
};
#endif
#ifdef HAVE_FNMATCH
inline bool
Glob::Check(const char *name_fs) const noexcept
{
return fnmatch(pattern.c_str(), name_fs, 0) == 0;
}
#endif
#endif /* HAVE_CLASS_GLOB */
#endif

@@ -29,7 +29,7 @@
NarrowPath::NarrowPath(Path _path) noexcept
:value(WideCharToMultiByte(CP_ACP, _path.c_str()))
{
if (value.IsNull())
if (value == nullptr)
/* fall back to empty string */
value = Value::Empty();
}

@@ -36,7 +36,7 @@
*/
class NarrowPath {
#ifdef _UNICODE
using Value = AllocatedString<>;
using Value = AllocatedString;
#else
using Value = StringPointer<>;
#endif

@@ -17,6 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef _WIN32
#undef NOUSER // COM needs the "MSG" typedef, and shlobj.h includes COM headers
#endif
#include "StandardDirectory.hxx"
#include "FileSystem.hxx"
#include "XDG.hxx"

@@ -42,7 +42,10 @@
#include <cstdint>
#ifdef _WIN32
#include <windows.h>
#include <fileapi.h>
#include <windef.h> // for HWND (needed by winbase.h)
#include <handleapi.h> // for INVALID_HANDLE_VALUE
#include <winbase.h> // for FILE_END
#endif
#if defined(__linux__) && !defined(ANDROID)

@@ -35,7 +35,10 @@
#include "util/Compiler.h"
#ifdef _WIN32
#include <windows.h>
#include <fileapi.h>
#include <handleapi.h> // for INVALID_HANDLE_VALUE
#include <windef.h> // for HWND (needed by winbase.h)
#include <winbase.h> // for FILE_CURRENT
#else
#include "io/UniqueFileDescriptor.hxx"
#endif

@@ -3,6 +3,7 @@ fs_sources = [
'Traits.cxx',
'Config.cxx',
'Charset.cxx',
'Glob.cxx',
'Path.cxx',
'Path2.cxx',
'AllocatedPath.cxx',

@@ -0,0 +1,46 @@
/*
* 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 "LastInputStream.hxx"
#include "InputStream.hxx"
#include <cassert>
LastInputStream::LastInputStream(EventLoop &event_loop) noexcept
:close_timer(event_loop, BIND_THIS_METHOD(OnCloseTimer))
{
}
LastInputStream::~LastInputStream() noexcept = default;
void
LastInputStream::Close() noexcept
{
uri.clear();
is.reset();
close_timer.Cancel();
}
void
LastInputStream::OnCloseTimer() noexcept
{
assert(is);
is.reset();
}

@@ -0,0 +1,86 @@
/*
* 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.
*/
#ifndef MPD_LAST_INPUT_STREAM_HXX
#define MPD_LAST_INPUT_STREAM_HXX
#include "Ptr.hxx"
#include "thread/Mutex.hxx"
#include "event/TimerEvent.hxx"
#include <string>
/**
* A helper class which maintains an #InputStream that is opened once
* and may be reused later for some time. It will be closed
* automatically after some time.
*
* This class is not thread-safe. All methods must be called on the
* thread which runs the #EventLoop.
*/
class LastInputStream {
std::string uri;
Mutex mutex;
InputStreamPtr is;
TimerEvent close_timer;
public:
explicit LastInputStream(EventLoop &event_loop) noexcept;
~LastInputStream() noexcept;
/**
* Open an #InputStream instance with the given opener
* function, but returns the cached instance if it matches.
*
* This object keeps owning the #InputStream; the caller shall
* not close it.
*/
template<typename U, typename O>
InputStream *Open(U &&new_uri, O &&open) {
if (new_uri == uri) {
if (is)
/* refresh the timeout */
ScheduleClose();
return is.get();
}
Close();
is = open(new_uri, mutex);
uri = std::forward<U>(new_uri);
if (is)
ScheduleClose();
return is.get();
}
void Close() noexcept;
private:
void ScheduleClose() noexcept {
close_timer.Schedule(std::chrono::seconds(20));
}
void OnCloseTimer() noexcept;
};
#endif

@@ -8,6 +8,7 @@ input_api = static_library(
'ThreadInputStream.cxx',
'AsyncInputStream.cxx',
'ProxyInputStream.cxx',
'LastInputStream.cxx',
include_directories: inc,
dependencies: [
boost_dep,

@@ -402,8 +402,8 @@ CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
{
request_headers.Append("Icy-Metadata: 1");
for (const auto &i : headers)
request_headers.Append((i.first + ":" + i.second).c_str());
for (const auto &[key, header] : headers)
request_headers.Append((key + ":" + header).c_str());
}
CurlInputStream::~CurlInputStream() noexcept
@@ -421,6 +421,10 @@ CurlInputStream::InitEasy()
request->SetOption(CURLOPT_MAXREDIRS, 5L);
request->SetOption(CURLOPT_FAILONERROR, 1L);
/* this option eliminates the probe request when
username/password are specified */
request->SetOption(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
if (proxy != nullptr)
request->SetOption(CURLOPT_PROXY, proxy);

@@ -174,8 +174,8 @@ QobuzClient::MakeUrl(const char *object, const char *method,
uri += method;
QueryStringBuilder q;
for (const auto &i : query)
q(uri, i.first.c_str(), i.second.c_str());
for (const auto &[key, url] : query)
q(uri, key.c_str(), url.c_str());
q(uri, "app_id", app_id);
return uri;
@@ -195,11 +195,11 @@ QobuzClient::MakeSignedUrl(const char *object, const char *method,
QueryStringBuilder q;
std::string concatenated_query(object);
concatenated_query += method;
for (const auto &i : query) {
q(uri, i.first.c_str(), i.second.c_str());
for (const auto &[key, url] : query) {
q(uri, key.c_str(), url.c_str());
concatenated_query += i.first;
concatenated_query += i.second;
concatenated_query += key;
concatenated_query += url;
}
q(uri, "app_id", app_id);

@@ -172,7 +172,15 @@ FileDescriptor::CreatePipe(FileDescriptor &r, FileDescriptor &w) noexcept
#endif
}
#ifndef _WIN32
#ifdef _WIN32
void
FileDescriptor::SetBinaryMode() noexcept
{
_setmode(fd, _O_BINARY);
}
#else // !_WIN32
bool
FileDescriptor::CreatePipeNonBlock(FileDescriptor &r,

@@ -148,10 +148,13 @@ public:
#ifdef _WIN32
void EnableCloseOnExec() noexcept {}
void DisableCloseOnExec() noexcept {}
void SetBinaryMode() noexcept;
#else
static bool CreatePipeNonBlock(FileDescriptor &r,
FileDescriptor &w) noexcept;
void SetBinaryMode() noexcept {}
/**
* Enable non-blocking mode on this file descriptor.
*/

@@ -36,16 +36,16 @@ EncodeForm(CURL *curl,
{
std::string result;
for (const auto &i : fields) {
for (const auto &[key, field] : fields) {
if (!result.empty())
result.push_back('&');
result.append(i.first);
result.append(key);
result.push_back('=');
if (!i.second.empty()) {
CurlString value(curl_easy_escape(curl, i.second.data(),
i.second.length()));
if (!field.empty()) {
CurlString value(
curl_easy_escape(curl, field.data(), field.length()));
if (value)
result.append(value);
}

@@ -1,19 +1,20 @@
diff -ur curl-7.63.0.orig/lib/url.c curl-7.63.0/lib/url.c
--- curl-7.63.0.orig/lib/url.c 2019-01-21 10:15:51.368019445 +0100
+++ curl-7.63.0/lib/url.c 2019-01-21 10:19:16.307523984 +0100
@@ -3057,6 +3057,7 @@
Index: curl-7.71.1/lib/url.c
===================================================================
--- curl-7.71.1.orig/lib/url.c
+++ curl-7.71.1/lib/url.c
@@ -2871,6 +2871,7 @@
}
conn->bits.netrc = FALSE;
+#ifndef __BIONIC__
if(data->set.use_netrc != CURL_NETRC_IGNORED &&
(!*userp || !**userp || !*passwdp || !**passwdp)) {
if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) {
bool netrc_user_changed = FALSE;
@@ -3090,6 +3091,7 @@
}
bool netrc_passwd_changed = FALSE;
@@ -2895,6 +2896,7 @@
conn->bits.user_passwd = TRUE; /* enable user+password */
}
}
+#endif
/* for updated strings, we update them in the URL */
if(user_changed) {
if(*userp) {

@@ -38,13 +38,13 @@
#include <string.h>
AllocatedString<>
AllocatedString
IcuCaseFold(std::string_view src) noexcept
try {
#ifdef HAVE_ICU
const auto u = UCharFromUTF8(src);
if (u.IsNull())
return AllocatedString<>::Duplicate(src);
return AllocatedString(src);
AllocatedArray<UChar> folded(u.size() * 2U);
@@ -54,7 +54,7 @@ try {
U_FOLD_CASE_DEFAULT,
&error_code);
if (folded_length == 0 || error_code != U_ZERO_ERROR)
return AllocatedString<>::Duplicate(src);
return AllocatedString(src);
folded.SetSize(folded_length);
return UCharToUTF8({folded.begin(), folded.size()});
@@ -63,7 +63,7 @@ try {
#error not implemented
#endif
} catch (...) {
return AllocatedString<>::Duplicate(src);
return AllocatedString(src);
}
#endif /* HAVE_ICU_CASE_FOLD */

@@ -27,9 +27,9 @@
#include <string_view>
template<typename T> class AllocatedString;
class AllocatedString;
AllocatedString<char>
AllocatedString
IcuCaseFold(std::string_view src) noexcept;
#endif

@@ -88,7 +88,7 @@ IcuCollate(std::string_view a, std::string_view b) noexcept
b.data(), b.size(), &code);
#elif defined(_WIN32)
AllocatedString<wchar_t> wa = nullptr, wb = nullptr;
BasicAllocatedString<wchar_t> wa, wb;
try {
wa = MultiByteToWideChar(CP_UTF8, a);

@@ -46,7 +46,7 @@ IcuCompare::IcuCompare(std::string_view _needle) noexcept
#else
IcuCompare::IcuCompare(std::string_view _needle) noexcept
:needle(AllocatedString<>::Duplicate(_needle)) {}
:needle(_needle) {}
#endif
@@ -56,7 +56,7 @@ IcuCompare::operator==(const char *haystack) const noexcept
#ifdef HAVE_ICU_CASE_FOLD
return StringIsEqual(IcuCaseFold(haystack).c_str(), needle.c_str());
#elif defined(_WIN32)
if (needle.IsNull())
if (needle == nullptr)
/* the MultiByteToWideChar() call in the constructor
has failed, so let's always fail the comparison */
return false;
@@ -83,7 +83,7 @@ IcuCompare::IsIn(const char *haystack) const noexcept
return StringFind(IcuCaseFold(haystack).c_str(),
needle.c_str()) != nullptr;
#elif defined(_WIN32)
if (needle.IsNull())
if (needle == nullptr)
/* the MultiByteToWideChar() call in the constructor
has failed, so let's always fail the comparison */
return false;

@@ -38,11 +38,11 @@ class IcuCompare {
#ifdef _WIN32
/* Windows API functions work with wchar_t strings, so let's
cache the MultiByteToWideChar() result for performance */
AllocatedString<wchar_t> needle;
#else
AllocatedString<> needle;
using AllocatedString = BasicAllocatedString<wchar_t>;
#endif
AllocatedString needle;
public:
IcuCompare():needle(nullptr) {}
@@ -50,12 +50,12 @@ public:
IcuCompare(const IcuCompare &src) noexcept
:needle(src
? src.needle.Clone()
? AllocatedString(src.needle)
: nullptr) {}
IcuCompare &operator=(const IcuCompare &src) noexcept {
needle = src
? src.needle.Clone()
? AllocatedString(src.needle)
: nullptr;
return *this;
}
@@ -65,7 +65,7 @@ public:
gcc_pure
operator bool() const noexcept {
return !needle.IsNull();
return needle != nullptr;
}
gcc_pure

@@ -77,7 +77,7 @@ IcuConverter::Create(const char *charset)
#ifdef HAVE_ICU
#elif defined(HAVE_ICONV)
static AllocatedString<char>
static AllocatedString
DoConvert(iconv_t conv, std::string_view src)
{
// TODO: dynamic buffer?
@@ -95,12 +95,12 @@ DoConvert(iconv_t conv, std::string_view src)
if (in_left > 0)
throw std::runtime_error("Charset conversion failed");
return AllocatedString<>::Duplicate({buffer, sizeof(buffer) - out_left});
return AllocatedString({buffer, sizeof(buffer) - out_left});
}
#endif
AllocatedString<char>
AllocatedString
IcuConverter::ToUTF8(std::string_view s) const
{
#ifdef HAVE_ICU
@@ -128,7 +128,7 @@ IcuConverter::ToUTF8(std::string_view s) const
#endif
}
AllocatedString<char>
AllocatedString
IcuConverter::FromUTF8(std::string_view s) const
{
#ifdef HAVE_ICU
@@ -151,7 +151,7 @@ IcuConverter::FromUTF8(std::string_view s) const
throw std::runtime_error(FormatString("Failed to convert from Unicode: %s",
u_errorName(code)).c_str());
return AllocatedString<>::Duplicate({buffer, size_t(target - buffer)});
return AllocatedString({buffer, size_t(target - buffer)});
#elif defined(HAVE_ICONV)
return DoConvert(from_utf8, s);

@@ -40,7 +40,7 @@
struct UConverter;
#endif
template<typename T> class AllocatedString;
class AllocatedString;
/**
* This class can convert strings with a certain character set to and
@@ -85,7 +85,7 @@ public:
* Throws std::runtime_error on error.
*/
gcc_nonnull_all
AllocatedString<char> ToUTF8(std::string_view s) const;
AllocatedString ToUTF8(std::string_view s) const;
/**
* Convert the string from UTF-8.
@@ -93,7 +93,7 @@ public:
* Throws std::runtime_error on error.
*/
gcc_nonnull_all
AllocatedString<char> FromUTF8(std::string_view s) const;
AllocatedString FromUTF8(std::string_view s) const;
};
#endif

@@ -48,7 +48,7 @@ UCharFromUTF8(std::string_view src)
return dest;
}
AllocatedString<>
AllocatedString
UCharToUTF8(std::basic_string_view<UChar> src)
{
/* worst-case estimate */
@@ -65,5 +65,5 @@ UCharToUTF8(std::basic_string_view<UChar> src)
throw std::runtime_error(u_errorName(error_code));
dest[dest_length] = 0;
return AllocatedString<>::Donate(dest.release());
return AllocatedString::Donate(dest.release());
}

@@ -25,7 +25,7 @@
#include <string_view>
template<typename T> class AllocatedArray;
template<typename T> class AllocatedString;
class AllocatedString;
/**
* Wrapper for u_strFromUTF8().
@@ -40,7 +40,7 @@ UCharFromUTF8(std::string_view src);
*
* Throws std::runtime_error on error.
*/
AllocatedString<char>
AllocatedString
UCharToUTF8(std::basic_string_view<UChar> src);
#endif

@@ -25,7 +25,7 @@
#include <windows.h>
AllocatedString<char>
AllocatedString
WideCharToMultiByte(unsigned code_page, std::wstring_view src)
{
int length = WideCharToMultiByte(code_page, 0, src.data(), src.size(),
@@ -42,10 +42,10 @@ WideCharToMultiByte(unsigned code_page, std::wstring_view src)
throw MakeLastError("Failed to convert from Unicode");
buffer[length] = '\0';
return AllocatedString<char>::Donate(buffer.release());
return AllocatedString::Donate(buffer.release());
}
AllocatedString<wchar_t>
BasicAllocatedString<wchar_t>
MultiByteToWideChar(unsigned code_page, std::string_view src)
{
int length = MultiByteToWideChar(code_page, 0, src.data(), src.size(),
@@ -60,5 +60,5 @@ MultiByteToWideChar(unsigned code_page, std::string_view src)
throw MakeLastError("Failed to convert to Unicode");
buffer[length] = L'\0';
return AllocatedString<wchar_t>::Donate(buffer.release());
return BasicAllocatedString<wchar_t>::Donate(buffer.release());
}

@@ -24,20 +24,21 @@
#include <string_view>
template<typename T> class AllocatedString;
class AllocatedString;
template<typename T> class BasicAllocatedString;
/**
* Throws std::system_error on error.
*/
gcc_pure gcc_nonnull_all
AllocatedString<char>
AllocatedString
WideCharToMultiByte(unsigned code_page, std::wstring_view src);
/**
* Throws std::system_error on error.
*/
gcc_pure gcc_nonnull_all
AllocatedString<wchar_t>
BasicAllocatedString<wchar_t>
MultiByteToWideChar(unsigned code_page, std::string_view src);
#endif

182
src/lib/jack/Dynamic.hxx Normal file

@@ -0,0 +1,182 @@
/*
* 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 "system/Error.hxx"
/* sorry for this horrible piece of code - there's no elegant way to
load DLLs at runtime */
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
using jack_set_error_function_t = std::add_pointer_t<decltype(jack_set_error_function)>;
static jack_set_error_function_t _jack_set_error_function;
using jack_set_info_function_t = std::add_pointer_t<decltype(jack_set_info_function)>;
static jack_set_info_function_t _jack_set_info_function;
using jack_client_open_t = std::add_pointer_t<decltype(jack_client_open)>;
static jack_client_open_t _jack_client_open;
using jack_client_close_t = std::add_pointer_t<decltype(jack_client_close)>;
static jack_client_close_t _jack_client_close;
using jack_connect_t = std::add_pointer_t<decltype(jack_connect)>;
static jack_connect_t _jack_connect;
using jack_activate_t = std::add_pointer_t<decltype(jack_activate)>;
static jack_activate_t _jack_activate;
using jack_deactivate_t = std::add_pointer_t<decltype(jack_deactivate)>;
static jack_deactivate_t _jack_deactivate;
using jack_get_sample_rate_t = std::add_pointer_t<decltype(jack_get_sample_rate)>;
static jack_get_sample_rate_t _jack_get_sample_rate;
using jack_set_process_callback_t = std::add_pointer_t<decltype(jack_set_process_callback)>;
static jack_set_process_callback_t _jack_set_process_callback;
using jack_on_info_shutdown_t = std::add_pointer_t<decltype(jack_on_info_shutdown)>;
static jack_on_info_shutdown_t _jack_on_info_shutdown;
using jack_free_t = std::add_pointer_t<decltype(jack_free)>;
static jack_free_t _jack_free;
using jack_get_ports_t = std::add_pointer_t<decltype(jack_get_ports)>;
static jack_get_ports_t _jack_get_ports;
using jack_port_register_t = std::add_pointer_t<decltype(jack_port_register)>;
static jack_port_register_t _jack_port_register;
using jack_port_name_t = std::add_pointer_t<decltype(jack_port_name)>;
static jack_port_name_t _jack_port_name;
using jack_port_get_buffer_t = std::add_pointer_t<decltype(jack_port_get_buffer)>;
static jack_port_get_buffer_t _jack_port_get_buffer;
using jack_ringbuffer_create_t = std::add_pointer_t<decltype(jack_ringbuffer_create)>;
static jack_ringbuffer_create_t _jack_ringbuffer_create;
using jack_ringbuffer_free_t = std::add_pointer_t<decltype(jack_ringbuffer_free)>;
static jack_ringbuffer_free_t _jack_ringbuffer_free;
using jack_ringbuffer_get_write_vector_t = std::add_pointer_t<decltype(jack_ringbuffer_get_write_vector)>;
static jack_ringbuffer_get_write_vector_t _jack_ringbuffer_get_write_vector;
using jack_ringbuffer_write_advance_t = std::add_pointer_t<decltype(jack_ringbuffer_write_advance)>;
static jack_ringbuffer_write_advance_t _jack_ringbuffer_write_advance;
using jack_ringbuffer_read_space_t = std::add_pointer_t<decltype(jack_ringbuffer_read_space)>;
static jack_ringbuffer_read_space_t _jack_ringbuffer_read_space;
using jack_ringbuffer_read_t = std::add_pointer_t<decltype(jack_ringbuffer_read)>;
static jack_ringbuffer_read_t _jack_ringbuffer_read;
using jack_ringbuffer_read_advance_t = std::add_pointer_t<decltype(jack_ringbuffer_read_advance)>;
static jack_ringbuffer_read_advance_t _jack_ringbuffer_read_advance;
using jack_ringbuffer_reset_t = std::add_pointer_t<decltype(jack_ringbuffer_reset)>;
static jack_ringbuffer_reset_t _jack_ringbuffer_reset;
template<typename T>
static void
GetFunction(HMODULE h, const char *name, T &result)
{
auto f = GetProcAddress(h, name);
if (f == nullptr)
throw FormatRuntimeError("No such libjack function: %s", name);
result = reinterpret_cast<T>(f);
}
static void
LoadJackLibrary()
{
#ifdef _WIN64
#define LIBJACK "libjack64"
#else
#define LIBJACK "libjack"
#endif
auto libjack = LoadLibraryA(LIBJACK);
if (!libjack)
throw FormatLastError("Failed to load " LIBJACK ".dll");
GetFunction(libjack, "jack_set_error_function", _jack_set_error_function);
GetFunction(libjack, "jack_set_info_function", _jack_set_info_function);
GetFunction(libjack, "jack_client_open", _jack_client_open);
GetFunction(libjack, "jack_client_close", _jack_client_close);
GetFunction(libjack, "jack_connect", _jack_connect);
GetFunction(libjack, "jack_activate", _jack_activate);
GetFunction(libjack, "jack_deactivate", _jack_deactivate);
GetFunction(libjack, "jack_free", _jack_free);
GetFunction(libjack, "jack_get_sample_rate", _jack_get_sample_rate);
GetFunction(libjack, "jack_set_process_callback", _jack_set_process_callback);
GetFunction(libjack, "jack_on_info_shutdown", _jack_on_info_shutdown);
GetFunction(libjack, "jack_get_ports", _jack_get_ports);
GetFunction(libjack, "jack_port_register", _jack_port_register);
GetFunction(libjack, "jack_port_name", _jack_port_name);
GetFunction(libjack, "jack_port_get_buffer", _jack_port_get_buffer);
GetFunction(libjack, "jack_ringbuffer_create", _jack_ringbuffer_create);
GetFunction(libjack, "jack_ringbuffer_free", _jack_ringbuffer_free);
GetFunction(libjack, "jack_ringbuffer_get_write_vector", _jack_ringbuffer_get_write_vector);
GetFunction(libjack, "jack_ringbuffer_write_advance", _jack_ringbuffer_write_advance);
GetFunction(libjack, "jack_ringbuffer_read_space", _jack_ringbuffer_read_space);
GetFunction(libjack, "jack_ringbuffer_read", _jack_ringbuffer_read);
GetFunction(libjack, "jack_ringbuffer_read_advance", _jack_ringbuffer_read_advance);
GetFunction(libjack, "jack_ringbuffer_reset", _jack_ringbuffer_reset);
}
#define jack_set_error_function _jack_set_error_function
#define jack_set_info_function _jack_set_info_function
#define jack_client_open _jack_client_open
#define jack_client_close _jack_client_close
#define jack_connect _jack_connect
#define jack_activate _jack_activate
#define jack_deactivate _jack_deactivate
#define jack_free _jack_free
#define jack_get_sample_rate _jack_get_sample_rate
#define jack_set_process_callback _jack_set_process_callback
#define jack_on_info_shutdown _jack_on_info_shutdown
#define jack_get_ports _jack_get_ports
#define jack_port_register _jack_port_register
#define jack_port_name _jack_port_name
#define jack_port_get_buffer _jack_port_get_buffer
#define jack_ringbuffer_create _jack_ringbuffer_create
#define jack_ringbuffer_free _jack_ringbuffer_free
#define jack_ringbuffer_get_write_vector _jack_ringbuffer_get_write_vector
#define jack_ringbuffer_write_advance _jack_ringbuffer_write_advance
#define jack_ringbuffer_read_space _jack_ringbuffer_read_space
#define jack_ringbuffer_read _jack_ringbuffer_read
#define jack_ringbuffer_read_advance _jack_ringbuffer_read_advance
#define jack_ringbuffer_reset _jack_ringbuffer_reset
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

@@ -1,5 +1,7 @@
if enable_database
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3',
fallback: ['sqlite3', 'sqlite3_dep'],
required: get_option('sqlite'))
else
sqlite_dep = dependency('', required: false)
endif
@@ -21,4 +23,7 @@ sqlite = static_library(
sqlite_dep = declare_dependency(
link_with: sqlite,
dependencies: [
sqlite_dep,
],
)

@@ -17,103 +17,102 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#undef NOUSER // COM needs the "MSG" typedef
#include "output/plugins/wasapi/ForMixer.hxx"
#include "output/plugins/wasapi/AudioClient.hxx"
#include "output/plugins/wasapi/Device.hxx"
#include "mixer/MixerInternal.hxx"
#include "output/plugins/WasapiOutputPlugin.hxx"
#include "win32/Com.hxx"
#include "win32/ComPtr.hxx"
#include "win32/ComWorker.hxx"
#include "win32/HResult.hxx"
#include <cmath>
#include <endpointvolume.h>
#include <optional>
#include <audioclient.h>
#include <endpointvolume.h>
#include <mmdeviceapi.h>
class WasapiMixer final : public Mixer {
WasapiOutput &output;
std::optional<COM> com;
public:
WasapiMixer(WasapiOutput &_output, MixerListener &_listener)
: Mixer(wasapi_mixer_plugin, _listener), output(_output) {}
void Open() override { com.emplace(); }
void Open() override {}
void Close() noexcept override { com.reset(); }
void Close() noexcept override {}
int GetVolume() override {
HRESULT result;
float volume_level;
auto com_worker = wasapi_output_get_com_worker(output);
if (!com_worker)
return -1;
if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume;
result = wasapi_output_get_device(output)->Activate(
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr,
endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get device endpoint volume");
auto future = com_worker->Async([&]() -> int {
HRESULT result;
float volume_level;
if (wasapi_is_exclusive(output)) {
auto endpoint_volume =
Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));
result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level);
if (FAILED(result)) {
throw MakeHResultError(result,
"Unable to get master "
"volume level");
}
} else {
auto session_volume =
GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));
result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw MakeHResultError(
result, "Unable to get master volume");
}
}
result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level);
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get master volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
result = wasapi_output_get_client(output)->GetService(
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get client session volume");
}
result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get master volume");
}
}
return std::lround(volume_level * 100.0f);
return std::lround(volume_level * 100.0f);
});
return future.get();
}
void SetVolume(unsigned volume) override {
HRESULT result;
const float volume_level = volume / 100.0f;
auto com_worker = wasapi_output_get_com_worker(output);
if (!com_worker)
throw std::runtime_error("Cannot set WASAPI volume");
if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume;
result = wasapi_output_get_device(output)->Activate(
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr,
endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get device endpoint volume");
}
com_worker->Async([&]() {
HRESULT result;
const float volume_level = volume / 100.0f;
result = endpoint_volume->SetMasterVolumeLevelScalar(volume_level,
nullptr);
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to set master volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
result = wasapi_output_get_client(output)->GetService(
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get client session volume");
}
if (wasapi_is_exclusive(output)) {
auto endpoint_volume =
Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));
result = session_volume->SetMasterVolume(volume_level, nullptr);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to set master volume");
result = endpoint_volume->SetMasterVolumeLevelScalar(
volume_level, nullptr);
if (FAILED(result)) {
throw MakeHResultError(
result,
"Unable to set master volume level");
}
} else {
auto session_volume =
GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));
result = session_volume->SetMasterVolume(volume_level,
nullptr);
if (FAILED(result)) {
throw MakeHResultError(
result, "Unable to set master volume");
}
}
}
}).get();
}
};

@@ -182,8 +182,8 @@ UdisksNeighborExplorer::GetList() const noexcept
NeighborExplorer::List result;
for (const auto &i : by_uri)
result.emplace_front(i.second);
for (const auto &[t, r] : by_uri)
result.emplace_front(r);
return result;
}

@@ -1,20 +1,30 @@
/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
* Copyright 2015-2021 Max Kellermann <max.kellermann@gmail.com>
*
* 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.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 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.
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 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.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "SocketError.hxx"

@@ -1,24 +1,34 @@
/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
* Copyright 2015-2021 Max Kellermann <max.kellermann@gmail.com>
*
* 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.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 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.
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 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.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef MPD_SOCKET_ERROR_HXX
#define MPD_SOCKET_ERROR_HXX
#ifndef SOCKET_ERROR_HXX
#define SOCKET_ERROR_HXX
#include "util/Compiler.h"
#include "system/Error.hxx"
@@ -42,14 +52,79 @@ GetSocketError() noexcept
#endif
}
gcc_const
static inline bool
IsSocketErrorAgain(socket_error_t code) noexcept
constexpr bool
IsSocketErrorInProgress(socket_error_t code) noexcept
{
#ifdef _WIN32
return code == WSAEINPROGRESS;
#else
return code == EAGAIN;
return code == EINPROGRESS;
#endif
}
constexpr bool
IsSocketErrorWouldBlock(socket_error_t code) noexcept
{
#ifdef _WIN32
return code == WSAEWOULDBLOCK;
#else
return code == EWOULDBLOCK;
#endif
}
constexpr bool
IsSocketErrorConnectWouldBlock(socket_error_t code) noexcept
{
#if defined(_WIN32) || defined(__linux__)
/* on Windows, WSAEINPROGRESS is for blocking sockets and
WSAEWOULDBLOCK for non-blocking sockets */
/* on Linux, EAGAIN==EWOULDBLOCK is for local sockets and
EINPROGRESS is for all other sockets */
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
#else
/* on all other operating systems, there's just EINPROGRESS */
return IsSocketErrorInProgress(code);
#endif
}
constexpr bool
IsSocketErrorSendWouldBlock(socket_error_t code) noexcept
{
#ifdef _WIN32
/* on Windows, WSAEINPROGRESS is for blocking sockets and
WSAEWOULDBLOCK for non-blocking sockets */
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
#else
/* on all other operating systems, there's just EAGAIN==EWOULDBLOCK */
return IsSocketErrorWouldBlock(code);
#endif
}
constexpr bool
IsSocketErrorReceiveWouldBlock(socket_error_t code) noexcept
{
#ifdef _WIN32
/* on Windows, WSAEINPROGRESS is for blocking sockets and
WSAEWOULDBLOCK for non-blocking sockets */
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
#else
/* on all other operating systems, there's just
EAGAIN==EWOULDBLOCK */
return IsSocketErrorWouldBlock(code);
#endif
}
constexpr bool
IsSocketErrorAcceptWouldBlock(socket_error_t code) noexcept
{
#ifdef _WIN32
/* on Windows, WSAEINPROGRESS is for blocking sockets and
WSAEWOULDBLOCK for non-blocking sockets */
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
#else
/* on all other operating systems, there's just
EAGAIN==EWOULDBLOCK */
return IsSocketErrorWouldBlock(code);
#endif
}

@@ -118,7 +118,7 @@ AudioOutputControl::GetLogName() const noexcept
{
assert(!IsDummy());
return output->GetLogName();
return output ? output->GetLogName() : name.c_str();
}
Mixer *
@@ -364,7 +364,7 @@ AudioOutputControl::LockPlay() noexcept
void
AudioOutputControl::LockPauseAsync() noexcept
{
if (output->mixer != nullptr && !output->SupportsPause())
if (output && output->mixer != nullptr && !output->SupportsPause())
/* the device has no pause mode: close the mixer,
unless its "global" flag is set (checked by
mixer_auto_close()) */

@@ -181,6 +181,14 @@ class AudioOutputControl {
*/
bool open = false;
/**
* Is the device currently playing, i.e. is its buffer
* (likely) non-empty? If not, then it will never be drained.
*
* This field is only valid while the output is open.
*/
bool playing;
/**
* Is the device paused? i.e. the output thread is in the
* ao_pause() loop.

@@ -40,8 +40,7 @@ printAudioDevices(Response &r, const MultipleOutputs &outputs)
ao.GetName(), ao.GetPluginName(),
ao.IsEnabled());
for (const auto &a : ao.GetAttributes())
r.Format("attribute: %s=%s\n",
a.first.c_str(), a.second.c_str());
for (const auto &[attribute, value] : ao.GetAttributes())
r.Format("attribute: %s=%s\n", attribute.c_str(), value.c_str());
}
}

@@ -42,7 +42,7 @@
#include "plugins/WinmmOutputPlugin.hxx"
#endif
#ifdef ENABLE_WASAPI_OUTPUT
#include "plugins/WasapiOutputPlugin.hxx"
#include "plugins/wasapi/WasapiOutputPlugin.hxx"
#endif
#include "util/StringAPI.hxx"

@@ -53,7 +53,7 @@ AudioOutputControl::InternalOpen2(const AudioFormat in_audio_format)
if (open && cf != output->filter_audio_format)
/* if the filter's output format changes, the output
must be reopened as well */
InternalCloseOutput(true);
InternalCloseOutput(playing);
output->filter_audio_format = cf;
@@ -64,6 +64,7 @@ AudioOutputControl::InternalOpen2(const AudioFormat in_audio_format)
}
open = true;
playing = false;
} else if (in_audio_format != output->out_audio_format) {
/* reconfigure the final ConvertFilter for its new
input AudioFormat */
@@ -285,6 +286,9 @@ AudioOutputControl::PlayChunk(std::unique_lock<Mutex> &lock) noexcept
assert(nbytes % output->out_audio_format.GetFrameSize() == 0);
source.ConsumeData(nbytes);
/* there's data to be drained from now on */
playing = true;
}
return true;
@@ -371,6 +375,9 @@ AudioOutputControl::InternalPause(std::unique_lock<Mutex> &lock) noexcept
}
skip_delay = true;
/* ignore drain commands until we got something new to play */
playing = false;
}
static void
@@ -390,6 +397,10 @@ PlayFull(FilteredAudioOutput &output, ConstBuffer<void> _buffer)
inline void
AudioOutputControl::InternalDrain() noexcept
{
/* after this method finishes, there's nothing left to be
drained */
playing = false;
try {
/* flush the filter and play its remaining output */
@@ -518,6 +529,7 @@ AudioOutputControl::Task() noexcept
source.Cancel();
if (open) {
playing = false;
const ScopeUnlock unlock(mutex);
output->Cancel();
}

@@ -44,6 +44,10 @@ static constexpr unsigned MAX_PORTS = 16;
static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
#ifdef DYNAMIC_JACK
#include "lib/jack/Dynamic.hxx"
#endif // _WIN32
class JackOutput final : public AudioOutput {
/**
* libjack options passed to jack_client_open().
@@ -463,6 +467,10 @@ JackOutput::Disable() noexcept
static AudioOutput *
mpd_jack_init(EventLoop &, const ConfigBlock &block)
{
#ifdef DYNAMIC_JACK
LoadJackLibrary();
#endif
jack_set_error_function(mpd_jack_error);
#ifdef HAVE_JACK_SET_INFO_FUNCTION

@@ -56,8 +56,6 @@ class PulseOutput final : AudioOutput {
size_t writable;
bool pause;
/**
* Was Interrupt() called? This will unblock Play(). It will
* be reset by Cancel() and Pause(), as documented by the
@@ -113,6 +111,7 @@ public:
[[nodiscard]] std::chrono::steady_clock::duration Delay() const noexcept override;
size_t Play(const void *chunk, size_t size) override;
void Drain() override;
void Cancel() noexcept override;
bool Pause() override;
@@ -688,7 +687,6 @@ PulseOutput::Open(AudioFormat &audio_format)
"pa_stream_connect_playback() has failed");
}
pause = false;
interrupted = false;
}
@@ -699,17 +697,6 @@ PulseOutput::Close() noexcept
Pulse::LockGuard lock(mainloop);
if (pa_stream_get_state(stream) == PA_STREAM_READY) {
pa_operation *o =
pa_stream_drain(stream,
pulse_output_stream_success_cb, this);
if (o == nullptr) {
LogPulseError(context,
"pa_stream_drain() has failed");
} else
pulse_wait_for_operation(mainloop, o);
}
DeleteStream();
if (context != nullptr &&
@@ -780,7 +767,7 @@ PulseOutput::Delay() const noexcept
Pulse::LockGuard lock(mainloop);
auto result = std::chrono::steady_clock::duration::zero();
if (pause && pa_stream_is_corked(stream) &&
if (pa_stream_is_corked(stream) &&
pa_stream_get_state(stream) == PA_STREAM_READY)
/* idle while paused */
result = std::chrono::seconds(1);
@@ -796,8 +783,6 @@ PulseOutput::Play(const void *chunk, size_t size)
Pulse::LockGuard lock(mainloop);
pause = false;
/* check if the stream is (already) connected */
WaitStream();
@@ -840,6 +825,25 @@ PulseOutput::Play(const void *chunk, size_t size)
return size;
}
void
PulseOutput::Drain()
{
Pulse::LockGuard lock(mainloop);
if (pa_stream_get_state(stream) != PA_STREAM_READY ||
pa_stream_is_suspended(stream) ||
pa_stream_is_corked(stream))
return;
pa_operation *o =
pa_stream_drain(stream,
pulse_output_stream_success_cb, this);
if (o == nullptr)
throw MakePulseError(context, "pa_stream_drain() failed");
pulse_wait_for_operation(mainloop, o);
}
void
PulseOutput::Cancel() noexcept
{
@@ -876,7 +880,6 @@ PulseOutput::Pause()
Pulse::LockGuard lock(mainloop);
pause = true;
interrupted = false;
/* check if the stream is (already/still) connected */

@@ -1,824 +0,0 @@
/*
* Copyright 2020-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 <initguid.h>
#include "Log.hxx"
#include "WasapiOutputPlugin.hxx"
#include "lib/icu/Win32.hxx"
#include "mixer/MixerList.hxx"
#include "thread/Cond.hxx"
#include "thread/Mutex.hxx"
#include "thread/Thread.hxx"
#include "util/AllocatedString.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx"
#include "win32/Com.hxx"
#include "win32/ComHeapPtr.hxx"
#include "win32/HResult.hxx"
#include "win32/WinEvent.hxx"
#include <algorithm>
#include <boost/lockfree/spsc_queue.hpp>
#include <cinttypes>
#include <cmath>
#include <functiondiscoverykeys_devpkey.h>
#include <optional>
#include <variant>
namespace {
static constexpr Domain wasapi_output_domain("wasapi_output");
gcc_const constexpr uint32_t GetChannelMask(const uint8_t channels) noexcept {
switch (channels) {
case 1:
return KSAUDIO_SPEAKER_MONO;
case 2:
return KSAUDIO_SPEAKER_STEREO;
case 3:
return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER;
case 4:
return KSAUDIO_SPEAKER_QUAD;
case 5:
return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
case 6:
return KSAUDIO_SPEAKER_5POINT1;
case 7:
return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER;
case 8:
return KSAUDIO_SPEAKER_7POINT1_SURROUND;
default:
gcc_unreachable();
}
}
template <typename Functor>
inline bool SafeTry(Functor &&functor) {
try {
functor();
return true;
} catch (std::runtime_error &err) {
FormatError(wasapi_output_domain, "%s", err.what());
return false;
}
}
template <typename Functor>
inline bool SafeSilenceTry(Functor &&functor) {
try {
functor();
return true;
} catch (std::runtime_error &err) {
return false;
}
}
inline void SetFormat(WAVEFORMATEXTENSIBLE &device_format,
const AudioFormat &audio_format) noexcept {
device_format.dwChannelMask = GetChannelMask(audio_format.channels);
device_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
device_format.Format.nChannels = audio_format.channels;
device_format.Format.nSamplesPerSec = audio_format.sample_rate;
device_format.Format.nBlockAlign = audio_format.GetFrameSize();
device_format.Format.nAvgBytesPerSec =
audio_format.sample_rate * audio_format.GetFrameSize();
device_format.Format.wBitsPerSample = audio_format.GetSampleSize() * 8;
device_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
device_format.Samples.wValidBitsPerSample = audio_format.GetSampleSize() * 8;
if (audio_format.format == SampleFormat::FLOAT) {
device_format.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
} else {
device_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
}
}
inline constexpr const unsigned int kErrorId = -1;
} // namespace
class WasapiOutputThread : public Thread {
public:
enum class Status : uint32_t { FINISH, PLAY, PAUSE };
WasapiOutputThread(std::shared_ptr<WinEvent> _event, ComPtr<IAudioClient> _client,
ComPtr<IAudioRenderClient> &&_render_client,
const UINT32 _frame_size, const UINT32 _buffer_size_in_frames,
bool _is_exclusive,
boost::lockfree::spsc_queue<BYTE> &_spsc_buffer)
: Thread(BIND_THIS_METHOD(Work)), event(std::move(_event)),
client(std::move(_client)), render_client(std::move(_render_client)),
frame_size(_frame_size), buffer_size_in_frames(_buffer_size_in_frames),
is_exclusive(_is_exclusive), spsc_buffer(_spsc_buffer) {}
void Finish() noexcept { return SetStatus(Status::FINISH); }
void Play() noexcept { return SetStatus(Status::PLAY); }
void Pause() noexcept { return SetStatus(Status::PAUSE); }
void WaitWrite() noexcept {
std::unique_lock<Mutex> lock(write.mutex);
write.cond.wait(lock);
}
void CheckException() {
std::unique_lock<Mutex> lock(error.mutex);
if (error.error_ptr) {
std::exception_ptr err = std::exchange(error.error_ptr, nullptr);
error.cond.notify_all();
std::rethrow_exception(err);
}
}
private:
std::shared_ptr<WinEvent> event;
std::optional<COM> com;
ComPtr<IAudioClient> client;
ComPtr<IAudioRenderClient> render_client;
const UINT32 frame_size;
const UINT32 buffer_size_in_frames;
bool is_exclusive;
boost::lockfree::spsc_queue<BYTE> &spsc_buffer;
alignas(BOOST_LOCKFREE_CACHELINE_BYTES) std::atomic<Status> status =
Status::PAUSE;
alignas(BOOST_LOCKFREE_CACHELINE_BYTES) struct {
Mutex mutex;
Cond cond;
} write{};
alignas(BOOST_LOCKFREE_CACHELINE_BYTES) struct {
Mutex mutex;
Cond cond;
std::exception_ptr error_ptr = nullptr;
} error{};
void SetStatus(Status s) noexcept {
status.store(s);
event->Set();
}
void Work() noexcept;
};
class WasapiOutput final : public AudioOutput {
public:
static AudioOutput *Create(EventLoop &, const ConfigBlock &block);
WasapiOutput(const ConfigBlock &block);
void Enable() override;
void Disable() noexcept override;
void Open(AudioFormat &audio_format) override;
void Close() noexcept override;
std::chrono::steady_clock::duration Delay() const noexcept override;
size_t Play(const void *chunk, size_t size) override;
void Drain() override;
bool Pause() override;
constexpr bool Exclusive() const { return is_exclusive; }
constexpr size_t FrameSize() const { return device_format.Format.nBlockAlign; }
constexpr size_t SampleRate() const {
return device_format.Format.nSamplesPerSec;
}
private:
bool is_started = false;
bool is_exclusive;
bool enumerate_devices;
std::string device_config;
std::vector<std::pair<unsigned int, AllocatedString<char>>> device_desc;
std::shared_ptr<WinEvent> event;
std::optional<COM> com;
ComPtr<IMMDeviceEnumerator> enumerator;
ComPtr<IMMDevice> device;
ComPtr<IAudioClient> client;
WAVEFORMATEXTENSIBLE device_format;
std::unique_ptr<WasapiOutputThread> thread;
std::unique_ptr<boost::lockfree::spsc_queue<BYTE>> spsc_buffer;
std::size_t watermark;
friend bool wasapi_is_exclusive(WasapiOutput &output) noexcept;
friend IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept;
friend IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept;
void FindExclusiveFormatSupported(AudioFormat &audio_format);
void FindSharedFormatSupported(AudioFormat &audio_format);
void EnumerateDevices();
void GetDevice(unsigned int index);
unsigned int SearchDevice(std::string_view name);
void GetDefaultDevice();
};
WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept {
return static_cast<WasapiOutput &>(output);
}
bool wasapi_is_exclusive(WasapiOutput &output) noexcept { return output.is_exclusive; }
IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept {
return output.device.get();
}
IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept {
return output.client.get();
}
void WasapiOutputThread::Work() noexcept {
FormatDebug(wasapi_output_domain, "Working thread started");
try {
com.emplace();
} catch (...) {
std::unique_lock<Mutex> lock(error.mutex);
error.error_ptr = std::current_exception();
error.cond.wait(lock);
assert(error.error_ptr == nullptr);
return;
}
while (true) {
try {
event->Wait(INFINITE);
Status current_state = status.load();
if (current_state == Status::FINISH) {
FormatDebug(wasapi_output_domain,
"Working thread stopped");
return;
}
AtScopeExit(&) { write.cond.notify_all(); };
HRESULT result;
UINT32 data_in_frames;
result = client->GetCurrentPadding(&data_in_frames);
if (FAILED(result)) {
throw FormatHResultError(result,
"Failed to get current padding");
}
UINT32 write_in_frames = buffer_size_in_frames;
if (!is_exclusive) {
if (data_in_frames >= buffer_size_in_frames) {
continue;
}
write_in_frames -= data_in_frames;
} else if (data_in_frames >= buffer_size_in_frames * 2) {
continue;
}
BYTE *data;
result = render_client->GetBuffer(write_in_frames, &data);
if (FAILED(result)) {
throw FormatHResultError(result, "Failed to get buffer");
}
AtScopeExit(&) {
render_client->ReleaseBuffer(write_in_frames, 0);
};
const UINT32 write_size = write_in_frames * frame_size;
UINT32 new_data_size = 0;
if (current_state == Status::PLAY) {
new_data_size = spsc_buffer.pop(data, write_size);
} else {
FormatDebug(wasapi_output_domain,
"Working thread paused");
}
std::fill_n(data + new_data_size, write_size - new_data_size, 0);
} catch (...) {
std::unique_lock<Mutex> lock(error.mutex);
error.error_ptr = std::current_exception();
error.cond.wait(lock);
assert(error.error_ptr == nullptr);
}
}
}
AudioOutput *WasapiOutput::Create(EventLoop &, const ConfigBlock &block) {
return new WasapiOutput(block);
}
WasapiOutput::WasapiOutput(const ConfigBlock &block)
: AudioOutput(FLAG_ENABLE_DISABLE | FLAG_PAUSE),
is_exclusive(block.GetBlockValue("exclusive", false)),
enumerate_devices(block.GetBlockValue("enumerate", false)),
device_config(block.GetBlockValue("device", "")) {}
void WasapiOutput::Enable() {
com.emplace();
event = std::make_shared<WinEvent>();
enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
CLSCTX_INPROC_SERVER);
device_desc.clear();
device.reset();
if (enumerate_devices && SafeTry([this]() { EnumerateDevices(); })) {
for (const auto &desc : device_desc) {
FormatNotice(wasapi_output_domain, "Device \"%u\" \"%s\"",
desc.first, desc.second.c_str());
}
}
unsigned int id = kErrorId;
if (!device_config.empty()) {
if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) {
id = SearchDevice(device_config);
}
}
if (id != kErrorId) {
SafeTry([this, id]() { GetDevice(id); });
}
if (!device) {
GetDefaultDevice();
}
device_desc.clear();
}
void WasapiOutput::Disable() noexcept {
if (thread) {
try {
thread->Finish();
thread->Join();
} catch (std::exception &err) {
FormatError(wasapi_output_domain, "exception while disabling: %s",
err.what());
}
thread.reset();
spsc_buffer.reset();
client.reset();
}
device.reset();
enumerator.reset();
com.reset();
event.reset();
}
void WasapiOutput::Open(AudioFormat &audio_format) {
if (audio_format.channels == 0) {
throw FormatInvalidArgument("channels should > 0");
}
client.reset();
HRESULT result;
result = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
client.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to activate audio client");
}
if (audio_format.format == SampleFormat::S24_P32) {
audio_format.format = SampleFormat::S32;
}
if (audio_format.channels > 8) {
audio_format.channels = 8;
}
if (Exclusive()) {
FindExclusiveFormatSupported(audio_format);
} else {
FindSharedFormatSupported(audio_format);
}
using s = std::chrono::seconds;
using ms = std::chrono::milliseconds;
using ns = std::chrono::nanoseconds;
using hundred_ns = std::chrono::duration<uint64_t, std::ratio<1, 10000000>>;
// The unit in REFERENCE_TIME is hundred nanoseconds
REFERENCE_TIME device_period;
result = client->GetDevicePeriod(&device_period, nullptr);
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to get device period");
}
FormatDebug(wasapi_output_domain, "Device period: %I64u ns",
size_t(ns(hundred_ns(device_period)).count()));
REFERENCE_TIME buffer_duration = device_period;
if (!Exclusive()) {
const REFERENCE_TIME align = hundred_ns(ms(50)).count();
buffer_duration = (align / device_period) * device_period;
}
FormatDebug(wasapi_output_domain, "Buffer duration: %I64u ns",
size_t(ns(hundred_ns(buffer_duration)).count()));
if (Exclusive()) {
result = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buffer_duration, buffer_duration,
reinterpret_cast<WAVEFORMATEX *>(&device_format), nullptr);
if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
UINT32 buffer_size_in_frames = 0;
result = client->GetBufferSize(&buffer_size_in_frames);
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get audio client buffer size");
}
buffer_duration = std::ceil(
double(buffer_size_in_frames * hundred_ns(s(1)).count()) /
SampleRate());
FormatDebug(wasapi_output_domain,
"Aligned buffer duration: %I64u ns",
size_t(ns(hundred_ns(buffer_duration)).count()));
client.reset();
result = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
nullptr, client.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to activate audio client");
}
result = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_NOPERSIST |
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buffer_duration, buffer_duration,
reinterpret_cast<WAVEFORMATEX *>(&device_format),
nullptr);
}
} else {
result = client->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buffer_duration, 0,
reinterpret_cast<WAVEFORMATEX *>(&device_format), nullptr);
}
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to initialize audio client");
}
ComPtr<IAudioRenderClient> render_client;
result = client->GetService(IID_PPV_ARGS(render_client.Address()));
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to get new render client");
}
result = client->SetEventHandle(event->handle());
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to set event handler");
}
UINT32 buffer_size_in_frames;
result = client->GetBufferSize(&buffer_size_in_frames);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get audio client buffer size");
}
watermark = buffer_size_in_frames * 3 * FrameSize();
spsc_buffer = std::make_unique<boost::lockfree::spsc_queue<BYTE>>(
buffer_size_in_frames * 4 * FrameSize());
thread = std::make_unique<WasapiOutputThread>(
event, client, std::move(render_client), FrameSize(),
buffer_size_in_frames, is_exclusive, *spsc_buffer);
thread->Start();
}
void WasapiOutput::Close() noexcept {
assert(client && thread);
Pause();
thread->Finish();
thread->Join();
thread.reset();
spsc_buffer.reset();
client.reset();
}
std::chrono::steady_clock::duration WasapiOutput::Delay() const noexcept {
if (!client || !is_started) {
return std::chrono::steady_clock::duration::zero();
}
const size_t data_size = spsc_buffer->read_available();
const size_t delay_size = std::max(data_size, watermark) - watermark;
using s = std::chrono::seconds;
using duration = std::chrono::steady_clock::duration;
auto result = duration(s(delay_size)) / device_format.Format.nAvgBytesPerSec;
return result;
}
size_t WasapiOutput::Play(const void *chunk, size_t size) {
if (!client || !thread) {
return 0;
}
do {
const size_t consumed_size =
spsc_buffer->push(static_cast<const BYTE *>(chunk), size);
if (consumed_size == 0) {
assert(is_started);
thread->WaitWrite();
continue;
}
if (!is_started) {
is_started = true;
thread->Play();
HRESULT result;
result = client->Start();
if (FAILED(result)) {
throw FormatHResultError(result,
"Failed to start client");
}
}
thread->CheckException();
return consumed_size;
} while (true);
}
void WasapiOutput::Drain() {
spsc_buffer->consume_all([](auto &&) {});
thread->CheckException();
}
bool WasapiOutput::Pause() {
if (!client || !thread) {
return false;
}
if (!is_started) {
return true;
}
HRESULT result;
result = client->Stop();
if (FAILED(result)) {
throw FormatHResultError(result, "Failed to stop client");
}
is_started = false;
thread->Pause();
thread->CheckException();
return true;
}
void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
SetFormat(device_format, audio_format);
do {
HRESULT result;
result = client->IsFormatSupported(
AUDCLNT_SHAREMODE_EXCLUSIVE,
reinterpret_cast<WAVEFORMATEX *>(&device_format), nullptr);
switch (result) {
case S_OK:
return;
case AUDCLNT_E_UNSUPPORTED_FORMAT:
break;
default:
throw FormatHResultError(result, "IsFormatSupported failed");
}
// Trying PCM fallback.
if (audio_format.format == SampleFormat::FLOAT) {
audio_format.format = SampleFormat::S32;
continue;
}
// Trying sample rate fallback.
if (audio_format.sample_rate > 96000) {
audio_format.sample_rate = 96000;
continue;
}
if (audio_format.sample_rate > 88200) {
audio_format.sample_rate = 88200;
continue;
}
if (audio_format.sample_rate > 64000) {
audio_format.sample_rate = 64000;
continue;
}
if (audio_format.sample_rate > 48000) {
audio_format.sample_rate = 48000;
continue;
}
// Trying 2 channels fallback.
if (audio_format.channels > 2) {
audio_format.channels = 2;
continue;
}
// Trying S16 fallback.
if (audio_format.format == SampleFormat::S32) {
audio_format.format = SampleFormat::S16;
continue;
}
if (audio_format.sample_rate > 41100) {
audio_format.sample_rate = 41100;
continue;
}
throw FormatHResultError(result, "Format is not supported");
} while (true);
}
void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
HRESULT result;
ComHeapPtr<WAVEFORMATEX> mixer_format;
// In shared mode, different sample rate is always unsupported.
result = client->GetMixFormat(mixer_format.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "GetMixFormat failed");
}
audio_format.sample_rate = device_format.Format.nSamplesPerSec;
SetFormat(device_format, audio_format);
ComHeapPtr<WAVEFORMATEXTENSIBLE> closest_format;
result = client->IsFormatSupported(
AUDCLNT_SHAREMODE_SHARED,
reinterpret_cast<WAVEFORMATEX *>(&device_format),
closest_format.AddressCast<WAVEFORMATEX>());
if (FAILED(result) && result != AUDCLNT_E_UNSUPPORTED_FORMAT) {
throw FormatHResultError(result, "IsFormatSupported failed");
}
switch (result) {
case S_OK:
break;
case AUDCLNT_E_UNSUPPORTED_FORMAT:
default:
// Trying channels fallback.
audio_format.channels = mixer_format->nChannels;
SetFormat(device_format, audio_format);
result = client->IsFormatSupported(
AUDCLNT_SHAREMODE_SHARED,
reinterpret_cast<WAVEFORMATEX *>(&device_format),
closest_format.AddressCast<WAVEFORMATEX>());
if (FAILED(result)) {
throw FormatHResultError(result, "Format is not supported");
}
break;
case S_FALSE:
if (closest_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
device_format = *closest_format;
} else {
device_format.Samples.wValidBitsPerSample =
device_format.Format.wBitsPerSample;
device_format.Format = closest_format->Format;
switch (std::exchange(device_format.Format.wFormatTag,
WAVE_FORMAT_EXTENSIBLE)) {
case WAVE_FORMAT_PCM:
device_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
break;
case WAVE_FORMAT_IEEE_FLOAT:
device_format.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
break;
default:
gcc_unreachable();
}
}
break;
}
// Copy closest match back to audio_format.
audio_format.channels = device_format.Format.nChannels;
audio_format.sample_rate = device_format.Format.nSamplesPerSec;
if (device_format.SubFormat == KSDATAFORMAT_SUBTYPE_PCM) {
switch (device_format.Format.wBitsPerSample) {
case 8:
audio_format.format = SampleFormat::S8;
break;
case 16:
audio_format.format = SampleFormat::S16;
break;
case 32:
audio_format.format = SampleFormat::S32;
break;
}
} else if (device_format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
audio_format.format = SampleFormat::FLOAT;
}
}
void WasapiOutput::EnumerateDevices() {
if (!device_desc.empty()) {
return;
}
HRESULT result;
ComPtr<IMMDeviceCollection> device_collection;
result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
device_collection.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to enumerate devices");
}
UINT count;
result = device_collection->GetCount(&count);
if (FAILED(result)) {
throw FormatHResultError(result, "Collection->GetCount failed");
}
device_desc.reserve(count);
for (UINT i = 0; i < count; ++i) {
ComPtr<IMMDevice> enumerated_device;
result = device_collection->Item(i, enumerated_device.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Collection->Item failed");
}
ComPtr<IPropertyStore> property_store;
result = enumerated_device->OpenPropertyStore(STGM_READ,
property_store.Address());
if (FAILED(result)) {
throw FormatHResultError(result,
"Device->OpenPropertyStore failed");
}
PROPVARIANT var_name;
PropVariantInit(&var_name);
AtScopeExit(&) { PropVariantClear(&var_name); };
result = property_store->GetValue(PKEY_Device_FriendlyName, &var_name);
if (FAILED(result)) {
throw FormatHResultError(result,
"PropertyStore->GetValue failed");
}
device_desc.emplace_back(
i, WideCharToMultiByte(CP_UTF8,
std::wstring_view(var_name.pwszVal)));
}
}
void WasapiOutput::GetDevice(unsigned int index) {
HRESULT result;
ComPtr<IMMDeviceCollection> device_collection;
result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
device_collection.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to enumerate devices");
}
result = device_collection->Item(index, device.Address());
if (FAILED(result)) {
throw FormatHResultError(result, "Collection->Item failed");
}
}
unsigned int WasapiOutput::SearchDevice(std::string_view name) {
if (!SafeTry([this]() { EnumerateDevices(); })) {
return kErrorId;
}
auto iter =
std::find_if(device_desc.cbegin(), device_desc.cend(),
[&name](const auto &desc) { return desc.second == name; });
if (iter == device_desc.cend()) {
FormatError(wasapi_output_domain, "Device %.*s not founded.",
int(name.size()), name.data());
return kErrorId;
}
FormatInfo(wasapi_output_domain, "Select device \"%u\" \"%s\"", iter->first,
iter->second.c_str());
return iter->first;
}
void WasapiOutput::GetDefaultDevice() {
HRESULT result;
result = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia,
device.Address());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get default device for multimedia");
}
}
static bool wasapi_output_test_default_device() { return true; }
const struct AudioOutputPlugin wasapi_output_plugin = {
"wasapi",
wasapi_output_test_default_device,
WasapiOutput::Create,
&wasapi_mixer_plugin,
};

@@ -28,6 +28,10 @@
#include <array>
#include <iterator>
#include <handleapi.h>
#include <synchapi.h>
#include <winbase.h> // for INFINITE
#include <stdlib.h>
#include <string.h>

@@ -26,7 +26,7 @@
#include "util/Compiler.h"
#include <windows.h>
#include <windef.h>
#include <mmsystem.h>
struct WinmmOutput;

@@ -136,7 +136,7 @@ bool
HttpdClient::SendResponse() noexcept
{
char buffer[1024];
AllocatedString<> allocated = nullptr;
AllocatedString allocated;
const char *response;
assert(state == State::RESPONSE);
@@ -162,6 +162,7 @@ HttpdClient::SendResponse() noexcept
"Connection: close\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache, no-store\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n",
httpd.content_type);
response = buffer;
@@ -278,7 +279,7 @@ HttpdClient::TryWrite() noexcept
metadata_current_position);
if (nbytes < 0) {
auto e = GetSocketError();
if (IsSocketErrorAgain(e))
if (IsSocketErrorSendWouldBlock(e))
return true;
if (!IsSocketErrorClosed(e)) {
@@ -305,7 +306,7 @@ HttpdClient::TryWrite() noexcept
ssize_t nbytes = GetSocket().Write(&empty_data, 1);
if (nbytes < 0) {
auto e = GetSocketError();
if (IsSocketErrorAgain(e))
if (IsSocketErrorSendWouldBlock(e))
return true;
if (!IsSocketErrorClosed(e)) {
@@ -328,7 +329,7 @@ HttpdClient::TryWrite() noexcept
bytes_to_write);
if (nbytes < 0) {
auto e = GetSocketError();
if (IsSocketErrorAgain(e))
if (IsSocketErrorSendWouldBlock(e))
return true;
if (!IsSocketErrorClosed(e)) {

@@ -27,7 +27,7 @@
#include <string.h>
AllocatedString<>
AllocatedString
icy_server_metadata_header(const char *name,
const char *genre, const char *url,
const char *content_type, int metaint) noexcept
@@ -45,6 +45,7 @@ icy_server_metadata_header(const char *name,
"Connection: close\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache, no-store\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n",
name,
genre,
@@ -54,7 +55,7 @@ icy_server_metadata_header(const char *name,
content_type);
}
static AllocatedString<>
static AllocatedString
icy_server_metadata_string(const char *stream_title,
const char* stream_url) noexcept
{
@@ -110,7 +111,7 @@ icy_server_metadata_page(const Tag &tag, const TagType *types) noexcept
const auto icy_string = icy_server_metadata_string(stream_title, "");
if (icy_string.IsNull())
if (icy_string == nullptr)
return nullptr;
return std::make_shared<Page>(icy_string.c_str(),

@@ -24,9 +24,9 @@
#include "tag/Type.h"
struct Tag;
template<typename T> class AllocatedString;
class AllocatedString;
AllocatedString<char>
AllocatedString
icy_server_metadata_header(const char *name,
const char *genre, const char *url,
const char *content_type, int metaint) noexcept;

@@ -136,11 +136,12 @@ endif
output_features.set('ENABLE_WASAPI_OUTPUT', is_windows)
if is_windows
output_plugins_sources += [
'WasapiOutputPlugin.cxx',
'wasapi/WasapiOutputPlugin.cxx',
]
wasapi_dep = [
c_compiler.find_library('ksuser', required: true),
c_compiler.find_library('ole32', required: true),
win32_dep,
]
else
wasapi_dep = dependency('', required: false)

@@ -0,0 +1,103 @@
/*
* Copyright 2020-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.
*/
#ifndef MPD_WASAPI_AUDIO_CLIENT_HXX
#define MPD_WASAPI_AUDIO_CLIENT_HXX
#include "win32/ComHeapPtr.hxx"
#include "win32/ComPtr.hxx"
#include "win32/HResult.hxx"
#include <audioclient.h>
inline UINT32
GetBufferSizeInFrames(IAudioClient &client)
{
UINT32 buffer_size_in_frames;
HRESULT result = client.GetBufferSize(&buffer_size_in_frames);
if (FAILED(result))
throw MakeHResultError(result,
"Unable to get audio client buffer size");
return buffer_size_in_frames;
}
inline UINT32
GetCurrentPaddingFrames(IAudioClient &client)
{
UINT32 padding_frames;
HRESULT result = client.GetCurrentPadding(&padding_frames);
if (FAILED(result))
throw MakeHResultError(result,
"Failed to get current padding");
return padding_frames;
}
inline ComHeapPtr<WAVEFORMATEX>
GetMixFormat(IAudioClient &client)
{
WAVEFORMATEX *f;
HRESULT result = client.GetMixFormat(&f);
if (FAILED(result))
throw MakeHResultError(result, "GetMixFormat failed");
return ComHeapPtr{f};
}
inline void
Start(IAudioClient &client)
{
HRESULT result = client.Start();
if (FAILED(result))
throw MakeHResultError(result, "Failed to start client");
}
inline void
Stop(IAudioClient &client)
{
HRESULT result = client.Stop();
if (FAILED(result))
throw MakeHResultError(result, "Failed to stop client");
}
inline void
SetEventHandle(IAudioClient &client, HANDLE h)
{
HRESULT result = client.SetEventHandle(h);
if (FAILED(result))
throw MakeHResultError(result, "Unable to set event handle");
}
template<typename T>
inline ComPtr<T>
GetService(IAudioClient &client)
{
T *p = nullptr;
HRESULT result = client.GetService(IID_PPV_ARGS(&p));
if (FAILED(result))
throw MakeHResultError(result, "Unable to get service");
return ComPtr{p};
}
#endif

@@ -0,0 +1,117 @@
/*
* Copyright 2020-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.
*/
#ifndef MPD_WASAPI_DEVICE_COLLECTION_HXX
#define MPD_WASAPI_DEVICE_COLLECTION_HXX
#include "win32/ComPtr.hxx"
#include "win32/HResult.hxx"
#include <mmdeviceapi.h>
inline ComPtr<IMMDevice>
GetDefaultAudioEndpoint(IMMDeviceEnumerator &e)
{
IMMDevice *device = nullptr;
HRESULT result = e.GetDefaultAudioEndpoint(eRender, eMultimedia,
&device);
if (FAILED(result))
throw MakeHResultError(result,
"Unable to get default device for multimedia");
return ComPtr{device};
}
inline ComPtr<IMMDeviceCollection>
EnumAudioEndpoints(IMMDeviceEnumerator &e)
{
IMMDeviceCollection *dc = nullptr;
HRESULT result = e.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
&dc);
if (FAILED(result))
throw MakeHResultError(result, "Unable to enumerate devices");
return ComPtr{dc};
}
inline UINT
GetCount(IMMDeviceCollection &dc)
{
UINT count;
HRESULT result = dc.GetCount(&count);
if (FAILED(result))
throw MakeHResultError(result, "Collection->GetCount failed");
return count;
}
inline ComPtr<IMMDevice>
Item(IMMDeviceCollection &dc, UINT i)
{
IMMDevice *device = nullptr;
auto result = dc.Item(i, &device);
if (FAILED(result))
throw MakeHResultError(result, "Collection->Item failed");
return ComPtr{device};
}
inline DWORD
GetState(IMMDevice &device)
{
DWORD state;
HRESULT result = device.GetState(&state);;
if (FAILED(result))
throw MakeHResultError(result, "Unable to get device status");
return state;
}
template<typename T>
inline ComPtr<T>
Activate(IMMDevice &device)
{
T *p = nullptr;
HRESULT result = device.Activate(__uuidof(T), CLSCTX_ALL,
nullptr, (void **)&p);
if (FAILED(result))
throw MakeHResultError(result, "Unable to activate device");
return ComPtr{p};
}
inline ComPtr<IPropertyStore>
OpenPropertyStore(IMMDevice &device)
{
IPropertyStore *property_store = nullptr;
HRESULT result = device.OpenPropertyStore(STGM_READ, &property_store);
if (FAILED(result))
throw MakeHResultError(result,
"Device->OpenPropertyStore failed");
return ComPtr{property_store};
}
#endif

@@ -0,0 +1,51 @@
/*
* Copyright 2020-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.
*/
#ifndef MPD_WASAPI_OUTPUT_FOR_MIXER_HXX
#define MPD_WASAPI_OUTPUT_FOR_MIXER_HXX
#include <memory>
struct IMMDevice;
struct IAudioClient;
class AudioOutput;
class WasapiOutput;
class COMWorker;
[[gnu::pure]]
WasapiOutput &
wasapi_output_downcast(AudioOutput &output) noexcept;
[[gnu::pure]]
bool
wasapi_is_exclusive(WasapiOutput &output) noexcept;
[[gnu::pure]]
std::shared_ptr<COMWorker>
wasapi_output_get_com_worker(WasapiOutput &output) noexcept;
[[gnu::pure]]
IMMDevice *
wasapi_output_get_device(WasapiOutput &output) noexcept;
[[gnu::pure]]
IAudioClient *
wasapi_output_get_client(WasapiOutput &output) noexcept;
#endif

@@ -0,0 +1,44 @@
/*
* Copyright 2020-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.
*/
#ifndef MPD_WASAPI_PROPERTY_STORE_HXX
#define MPD_WASAPI_PROPERTY_STORE_HXX
#include "win32/PropVariant.hxx"
#include "util/AllocatedString.hxx"
#include "util/ScopeExit.hxx"
#include <propsys.h>
[[gnu::pure]]
inline AllocatedString
GetString(IPropertyStore &ps, REFPROPERTYKEY key) noexcept
{
PROPVARIANT pv;
PropVariantInit(&pv);
HRESULT result = ps.GetValue(key, &pv);
if (FAILED(result))
return nullptr;
AtScopeExit(&) { PropVariantClear(&pv); };
return ToString(pv);
}
#endif

File diff suppressed because it is too large Load Diff

@@ -20,25 +20,6 @@
#ifndef MPD_WASAPI_OUTPUT_PLUGIN_HXX
#define MPD_WASAPI_OUTPUT_PLUGIN_HXX
#include "output/Features.h"
#include "../OutputAPI.hxx"
#include "util/Compiler.h"
#include "win32/ComPtr.hxx"
#include <audioclient.h>
#include <mmdeviceapi.h>
extern const struct AudioOutputPlugin wasapi_output_plugin;
class WasapiOutput;
gcc_pure WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept;
gcc_pure bool wasapi_is_exclusive(WasapiOutput &output) noexcept;
gcc_pure IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept;
gcc_pure IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept;
#endif

@@ -94,7 +94,7 @@ ParseCommandArgRange(const char *s)
s);
if (test == test2)
value = std::numeric_limits<int>::max();
return RangeArg::OpenEnded(range.start);
if (value < 0)
throw FormatProtocolError(ACK_ERROR_ARG,
@@ -107,9 +107,13 @@ ParseCommandArgRange(const char *s)
range.end = (unsigned)value;
} else {
range.end = (unsigned)value + 1;
return RangeArg::Single(range.start);
}
if (!range.IsWellFormed())
throw FormatProtocolError(ACK_ERROR_ARG,
"Malformed range: %s", s);
return range;
}

@@ -25,8 +25,22 @@
struct RangeArg {
unsigned start, end;
static constexpr RangeArg All() {
return { 0, std::numeric_limits<unsigned>::max() };
/**
* Construct an open-ended range starting at the given index.
*/
static constexpr RangeArg OpenEnded(unsigned start) noexcept {
return { start, std::numeric_limits<unsigned>::max() };
}
static constexpr RangeArg All() noexcept {
return OpenEnded(0);
}
/**
* Construct an instance describing exactly one index.
*/
static constexpr RangeArg Single(unsigned i) noexcept {
return { i, i + 1 };
}
constexpr bool operator==(RangeArg other) const noexcept {
@@ -37,13 +51,45 @@ struct RangeArg {
return !(*this == other);
}
constexpr bool IsOpenEnded() const noexcept {
return end == All().end;
}
constexpr bool IsAll() const noexcept {
return *this == All();
}
constexpr bool IsWellFormed() const noexcept {
return start <= end;
}
/**
* Is this range empty? A malformed range also counts as
* "empty" for this method.
*/
constexpr bool IsEmpty() const noexcept {
return start >= end;
}
/**
* Check if the range contains at least this number of items.
* Unlike Count(), this allows the object to be malformed.
*/
constexpr bool HasAtLeast(unsigned n) const noexcept {
return start + n <= end;
}
constexpr bool Contains(unsigned i) const noexcept {
return i >= start && i < end;
}
/**
* Count the number of items covered by this range. This requires the
* object to be well-formed.
*/
constexpr unsigned Count() const noexcept {
return end - start;
}
};
#endif

@@ -44,13 +44,13 @@ playlist::TagModified(DetachedSong &&song) noexcept
}
void
playlist::TagModified(const char *uri, const Tag &tag) noexcept
playlist::TagModified(const char *real_uri, const Tag &tag) noexcept
{
bool modified = false;
for (unsigned i = 0; i < queue.length; ++i) {
auto &song = *queue.items[i].song;
if (song.IsURI(uri)) {
if (song.IsRealURI(real_uri)) {
song.SetTag(tag);
queue.ModifyAtPosition(i);
modified = true;

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