Commit d5358db7 authored by Konstantin A. Lepikhov's avatar Konstantin A. Lepikhov

Merge tag 'v0.23.11' of https://github.com/MusicPlayerDaemon/MPD into sisyphus

release v0.23.11
parents 8ae0408e 9866adff
ver 0.23.11 (2022/11/28)
* database
- simple: move default database to ~/.cache/mpd/db from ~/.cache/mpd.db
- simple: default "cache_directory" to ~/.cache/mpd/mounts
* macOS: fix build failure "no archive members specified"
* Windows
- fix crash bug (stack buffer overflow) after I/O errors
- fix path traversal bug because backslash was allowed in playlist names
* Android/Windows
- update OpenSSL to 3.0.7
- re-enable CURL's verbose error strings
ver 0.23.10 (2022/10/14)
* storage
- curl: fix file time stamps
* decoder
- ffmpeg: fix libfmt 9 compiler warning
* encoder
- flac: fix failure when libFLAC is built without Ogg support
* output
- alsa: fix crash bug
* Windows
- log to stdout by default, don't require "log_file" setting
ver 0.23.9 (2022/08/18)
* input
- cdio_paranoia: add options "mode" and "skip"
* decoder
- ffmpeg: support FFmpeg 5.1
* filter
- replay gain: fix delayed volume display with handler=mixer
* output
- pipewire: set app icon
* fix bogus volume levels with multiple partitions
* improve iconv detection
* macOS: fix macOS 10 build problem (0.23.8 regression)
* Android
- load mpd.conf from app data directory
ver 0.23.8 (2022/07/09) ver 0.23.8 (2022/07/09)
* storage * storage
- curl: fix crash if web server does not understand WebDAV - curl: fix crash if web server does not understand WebDAV
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd" package="org.musicpd"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="66" android:versionCode="70"
android:versionName="0.23.7"> android:versionName="0.23.11">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
<uses-feature android:name="android.software.leanback" <uses-feature android:name="android.software.leanback"
android:required="false" /> android:required="false" />
......
...@@ -12,18 +12,30 @@ unsigned_apk = custom_target( ...@@ -12,18 +12,30 @@ unsigned_apk = custom_target(
], ],
) )
aligned_apk = custom_target(
'mpd-aligned.apk',
output: 'mpd-aligned.apk',
input: unsigned_apk,
command: [
android_zipalign,
'-f', '4',
'@INPUT@', '@OUTPUT@',
],
)
if get_option('android_debug_keystore') != '' if get_option('android_debug_keystore') != ''
debug_apk = custom_target( debug_apk = custom_target(
'mpd-debug.apk', 'mpd-debug.apk',
output: 'mpd-debug.apk', output: 'mpd-debug.apk',
input: unsigned_apk, input: aligned_apk,
command: [ command: [
jarsigner, apksigner, 'sign',
'-keystore', get_option('android_debug_keystore'), '--in', '@INPUT@',
'-storepass', 'android', '--out', '@OUTPUT@',
'-signedjar', '@OUTPUT@', '--debuggable-apk-permitted',
'@INPUT@', '-ks', get_option('android_debug_keystore'),
'androiddebugkey', '--ks-key-alias', 'androiddebugkey',
'--ks-pass', 'pass:android',
], ],
build_by_default: true build_by_default: true
) )
...@@ -31,29 +43,16 @@ endif ...@@ -31,29 +43,16 @@ endif
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != '' if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
unaligned_apk = custom_target( unaligned_apk = custom_target(
'mpd-unaligned.apk',
output: 'mpd-unaligned.apk',
input: unsigned_apk,
command: [
jarsigner,
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
'-keystore', get_option('android_keystore'),
'-storepass', get_option('android_keypass'),
'-signedjar', '@OUTPUT@',
'@INPUT@',
get_option('android_keyalias'),
],
)
apk = custom_target(
'mpd.apk', 'mpd.apk',
output: 'mpd.apk', output: 'mpd.apk',
input: unaligned_apk, input: aligned_apk,
command: [ command: [
android_zipalign, apksigner, 'sign',
'-f', '4', '--in', '@INPUT@',
'@INPUT@', '@OUTPUT@', '--out', '@OUTPUT@',
'-ks', get_option('android_keystore'),
'--ks-key-alias', get_option('android_keyalias'),
'--ks-pass', 'pass:' + get_option('android_keypass'),
], ],
build_by_default: true
) )
endif endif
#!/usr/bin/env python3 #!/usr/bin/env -S python3 -u
import os, os.path import os, os.path
import sys, subprocess import sys, subprocess
......
...@@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx') ...@@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
android_zipalign = join_paths(android_build_tools_dir, 'zipalign') android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
javac = find_program('javac') javac = find_program('javac')
jarsigner = find_program('jarsigner') apksigner = find_program('apksigner')
rsvg_convert = find_program('rsvg-convert') rsvg_convert = find_program('rsvg-convert')
convert = find_program('convert') convert = find_program('convert')
zip = find_program('zip') zip = find_program('zip')
......
...@@ -38,7 +38,10 @@ author = 'Max Kellermann' ...@@ -38,7 +38,10 @@ author = 'Max Kellermann'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.23.8' with open('../meson.build') as f:
import re
version = re.match(r"project\([^\)]*\bversion:\s*'([^']+)'",
f.read(4096)).group(1)
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
#release = version + '~git' #release = version + '~git'
...@@ -47,7 +50,7 @@ version = '0.23.8' ...@@ -47,7 +50,7 @@ version = '0.23.8'
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = None language = "en"
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
......
...@@ -11,6 +11,12 @@ Music Player Daemon ...@@ -11,6 +11,12 @@ Music Player Daemon
client client
protocol protocol
.. toctree::
:maxdepth: 1
:caption: man pages:
mpd.1
mpd.conf.5
Indices and tables Indices and tables
================== ==================
......
...@@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]". ...@@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
- If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this. - If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
* - **speed N** * - **speed N**
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet. - Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
* - **mode disable|overlap|full**
- Set the paranoia mode; ``disable`` means no fixups, ``overlap``
performs overlapped reads, and ``full`` enables all options.
* - **skip yes|no**
- If set to ``no``, then never skip failed reads.
curl curl
---- ----
...@@ -214,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl. ...@@ -214,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl.
Note that unless overridden by the below settings (e.g. by setting Note that unless overridden by the below settings (e.g. by setting
them to a blank value), general curl configuration from environment them to a blank value), general curl configuration from environment
variables such as ``http_proxy`` or specified in :file:`~/.curlrc` variables such as ``http_proxy`` will be in effect.
will be in effect.
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
.. list-table:: .. list-table::
:widths: 20 80 :widths: 20 80
......
...@@ -36,7 +36,9 @@ Installing on Android ...@@ -36,7 +36,9 @@ Installing on Android
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client. An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function). If you need to tweak the configuration, you can create a file called
:file:`mpd.conf` in MPD's data directory on the external storage
(usually :file:`Android/data/org.musicpd/files/mpd.conf`).
ALSA is not available on Android; only the :ref:`OpenSL ES ALSA is not available on Android; only the :ref:`OpenSL ES
<sles_output>` output plugin can be used for local playback. <sles_output>` output plugin can be used for local playback.
...@@ -197,7 +199,7 @@ Compiling for Android ...@@ -197,7 +199,7 @@ Compiling for Android
You need: You need:
* Android SDK * Android SDK
* `Android NDK r23 <https://developer.android.com/ndk/downloads>`_ * `Android NDK r25b <https://developer.android.com/ndk/downloads>`_
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja * `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__ <https://ninja-build.org/>`__
* cmake * cmake
......
project( project(
'mpd', 'mpd',
['c', 'cpp'], ['c', 'cpp'],
version: '0.23.8', version: '0.23.11',
meson_version: '>= 0.56.0', meson_version: '>= 0.56.0',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
...@@ -251,6 +251,14 @@ endif ...@@ -251,6 +251,14 @@ endif
fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep']) fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
if compiler.get_id() == 'clang' and compiler.version().version_compare('<15')
fmt_dep = declare_dependency(
dependencies: fmt_dep,
# suppress bogus clang 14 warning (the version in Android NDK r25b)
compile_args: ['-Wno-unused-local-typedef'],
)
endif
log = static_library( log = static_library(
'log', 'log',
'src/Log.cxx', 'src/Log.cxx',
...@@ -352,7 +360,7 @@ sources = [ ...@@ -352,7 +360,7 @@ sources = [
'src/TagStream.cxx', 'src/TagStream.cxx',
'src/TagAny.cxx', 'src/TagAny.cxx',
'src/TimePrint.cxx', 'src/TimePrint.cxx',
'src/mixer/Volume.cxx', 'src/mixer/Memento.cxx',
'src/PlaylistFile.cxx', 'src/PlaylistFile.cxx',
] ]
...@@ -382,6 +390,7 @@ endif ...@@ -382,6 +390,7 @@ endif
if enable_database if enable_database
sources += [ sources += [
'src/storage/StorageState.cxx',
'src/queue/PlaylistUpdate.cxx', 'src/queue/PlaylistUpdate.cxx',
'src/command/StorageCommands.cxx', 'src/command/StorageCommands.cxx',
'src/command/DatabaseCommands.cxx', 'src/command/DatabaseCommands.cxx',
......
...@@ -43,20 +43,22 @@ opus = AutotoolsProject( ...@@ -43,20 +43,22 @@ opus = AutotoolsProject(
) )
flac = AutotoolsProject( flac = AutotoolsProject(
'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz', 'http://downloads.xiph.org/releases/flac/flac-1.4.2.tar.xz',
'8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737', 'e322d58a1f48d23d9dd38f432672865f6f79e73a6f9cc5a5f57fcaa83eb5a8e4',
'lib/libFLAC.a', 'lib/libFLAC.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
'--disable-stack-smash-protection',
'--disable-xmms-plugin', '--disable-cpplibs', '--disable-xmms-plugin', '--disable-cpplibs',
'--disable-doxygen-docs', '--disable-doxygen-docs',
'--disable-programs',
], ],
subdirs=['include', 'src/libFLAC'], subdirs=['include', 'src/libFLAC'],
) )
zlib = ZlibProject( zlib = ZlibProject(
'http://zlib.net/zlib-1.2.12.tar.xz', 'http://zlib.net/zlib-1.2.13.tar.xz',
'7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18', 'd14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98',
'lib/libz.a', 'lib/libz.a',
) )
...@@ -112,16 +114,20 @@ libmodplug = AutotoolsProject( ...@@ -112,16 +114,20 @@ libmodplug = AutotoolsProject(
) )
libopenmpt = AutotoolsProject( libopenmpt = AutotoolsProject(
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz', 'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af', '6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
'lib/libopenmpt.a', 'lib/libopenmpt.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
'--disable-openmpt123', '--disable-openmpt123',
'--disable-examples',
'--disable-tests',
'--disable-doxygen-doc',
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile', '--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile', '--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
'--without-flac',
], ],
base='libopenmpt-0.5.12+release.autotools', base='libopenmpt-0.6.6+release.autotools',
) )
wildmidi = CmakeProject( wildmidi = CmakeProject(
...@@ -151,8 +157,8 @@ gme = CmakeProject( ...@@ -151,8 +157,8 @@ gme = CmakeProject(
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-5.1.2.tar.xz',
'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b', '619e706d662c8420859832ddc259cd4d4096a48a2ce1eefd052db9e440eef3dc',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -166,7 +172,6 @@ ffmpeg = FfmpegProject( ...@@ -166,7 +172,6 @@ ffmpeg = FfmpegProject(
'--disable-swscale', '--disable-swscale',
'--disable-postproc', '--disable-postproc',
'--disable-avfilter', '--disable-avfilter',
'--disable-lzo',
'--disable-faan', '--disable-faan',
'--disable-pixelutils', '--disable-pixelutils',
'--disable-network', '--disable-network',
...@@ -382,19 +387,18 @@ ffmpeg = FfmpegProject( ...@@ -382,19 +387,18 @@ ffmpeg = FfmpegProject(
) )
openssl = OpenSSLProject( openssl = OpenSSLProject(
'https://www.openssl.org/source/openssl-3.0.5.tar.gz', 'https://www.openssl.org/source/openssl-3.0.7.tar.gz',
'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a', '83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e',
'include/openssl/ossl_typ.h', 'include/openssl/ossl_typ.h',
) )
curl = CmakeProject( curl = CmakeProject(
'https://curl.se/download/curl-7.84.0.tar.xz', 'https://curl.se/download/curl-7.86.0.tar.xz',
'2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8', '2d61116e5f485581f6d59865377df4463f2e788677ac43222b496d4e49fb627b',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'-DBUILD_CURL_EXE=OFF', '-DBUILD_CURL_EXE=OFF',
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
'-DCURL_DISABLE_LDAP=ON', '-DCURL_DISABLE_LDAP=ON',
'-DCURL_DISABLE_TELNET=ON', '-DCURL_DISABLE_TELNET=ON',
'-DCURL_DISABLE_DICT=ON', '-DCURL_DISABLE_DICT=ON',
...@@ -423,8 +427,8 @@ curl = CmakeProject( ...@@ -423,8 +427,8 @@ curl = CmakeProject(
) )
libnfs = AutotoolsProject( libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz', 'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
'7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8', '637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
'lib/libnfs.a', 'lib/libnfs.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
...@@ -435,7 +439,7 @@ libnfs = AutotoolsProject( ...@@ -435,7 +439,7 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples', '--disable-utils', '--disable-examples',
], ],
base='libnfs-libnfs-5.0.1', base='libnfs-libnfs-5.0.2',
autoreconf=True, autoreconf=True,
) )
...@@ -446,7 +450,7 @@ jack = JackProject( ...@@ -446,7 +450,7 @@ jack = JackProject(
) )
boost = BoostProject( boost = BoostProject(
'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2', 'https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2',
'475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39', '1e19565d82e43bc59209a168f5ac899d3ba471d55c7610c677d4ccf2c9c500c0',
'include/boost/version.hpp', 'include/boost/version.hpp',
) )
...@@ -82,8 +82,8 @@ endian = '{endian}' ...@@ -82,8 +82,8 @@ endian = '{endian}'
def configure(toolchain, src, build, args=()): def configure(toolchain, src, build, args=()):
cross_file = make_cross_file(toolchain) cross_file = make_cross_file(toolchain)
configure = [ configure = [
'meson', 'meson', 'setup',
src, build, build, src,
'--prefix', toolchain.install_prefix, '--prefix', toolchain.install_prefix,
......
...@@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout) ...@@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
getenv("NOTIFY_SOCKET") != nullptr) { getenv("NOTIFY_SOCKET") != nullptr) {
/* if MPD was started as a systemd /* if MPD was started as a systemd
service, default to journal (which service, default to journal (which
is connected to fd=2) */ is connected to stdout&stderr) */
out_fd = STDOUT_FILENO; out_fd = STDOUT_FILENO;
return; return;
} }
#endif #endif
#ifndef HAVE_SYSLOG #ifdef _WIN32
/* default to stdout on Windows */
out_fd = STDOUT_FILENO;
#elif !defined(HAVE_SYSLOG)
throw std::runtime_error("config parameter 'log_file' not found"); throw std::runtime_error("config parameter 'log_file' not found");
#endif #endif
#ifdef HAVE_SYSLOG #ifdef HAVE_SYSLOG
......
...@@ -590,19 +590,46 @@ MainConfigured(const CommandLineOptions &options, ...@@ -590,19 +590,46 @@ MainConfigured(const CommandLineOptions &options,
#ifdef ANDROID #ifdef ANDROID
/**
* Wrapper for ReadConfigFile() which returns false if the file was
* not found.
*/
static bool
TryReadConfigFile(ConfigData &config, Path path)
{
if (!FileExists(path))
return false;
ReadConfigFile(config, path);
return true;
}
static void
LoadConfigFile(JNIEnv *env, ConfigData &config)
{
/* try loading mpd.conf from
"Android/data/org.musicpd/files/mpd.conf" (the app specific
data directory) first */
if (const auto dir = context->GetExternalFilesDir(env);
!dir.IsNull() &&
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")))
return;
/* if that fails, attempt to load "mpd.conf" from the root of
the SD card (pre-0.23.9, ceases to work since Android
12) */
if (const auto dir = Environment::getExternalStorageDirectory(env);
!dir.IsNull())
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"));
}
static void static void
AndroidMain() AndroidMain(JNIEnv *env)
{ {
CommandLineOptions options; CommandLineOptions options;
ConfigData raw_config; ConfigData raw_config;
const auto sdcard = Environment::getExternalStorageDirectory(); LoadConfigFile(env, raw_config);
if (!sdcard.IsNull()) {
const auto config_path =
sdcard / Path::FromFS("mpd.conf");
if (FileExists(config_path))
ReadConfigFile(raw_config, config_path);
}
MainConfigured(options, raw_config); MainConfigured(options, raw_config);
} }
...@@ -614,9 +641,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL ...@@ -614,9 +641,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
Java::Init(env); Java::Init(env);
Java::Object::Initialise(env); Java::Object::Initialise(env);
Java::File::Initialise(env); Java::File::Initialise(env);
Environment::Initialise(env); Environment::Initialise(env);
AtScopeExit(env) { Environment::Deinitialise(env); }; AtScopeExit(env) { Environment::Deinitialise(env); };
Context::Initialise(env);
context = new Context(env, _context); context = new Context(env, _context);
AtScopeExit() { delete context; }; AtScopeExit() { delete context; };
...@@ -625,7 +655,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL ...@@ -625,7 +655,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
AtScopeExit() { delete logListener; }; AtScopeExit() { delete logListener; };
try { try {
AndroidMain(); AndroidMain(env);
} catch (...) { } catch (...) {
LogError(std::current_exception()); LogError(std::current_exception());
} }
......
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
#include "Log.hxx" #include "Log.hxx"
#include "lib/fmt/ExceptionFormatter.hxx" #include "lib/fmt/ExceptionFormatter.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "mixer/Volume.hxx"
#include "IdleFlags.hxx" #include "IdleFlags.hxx"
#include "client/Listener.hxx" #include "client/Listener.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
...@@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept ...@@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
void void
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
{ {
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
/* notify clients */ /* notify clients */
EmitIdle(IDLE_MIXER); EmitIdle(IDLE_MIXER);
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "queue/Listener.hxx" #include "queue/Listener.hxx"
#include "output/MultipleOutputs.hxx" #include "output/MultipleOutputs.hxx"
#include "mixer/Listener.hxx" #include "mixer/Listener.hxx"
#include "mixer/Memento.hxx"
#include "player/Control.hxx" #include "player/Control.hxx"
#include "player/Listener.hxx" #include "player/Listener.hxx"
#include "protocol/RangeArg.hxx" #include "protocol/RangeArg.hxx"
...@@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { ...@@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
MultipleOutputs outputs; MultipleOutputs outputs;
MixerMemento mixer_memento;
PlayerControl pc; PlayerControl pc;
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
......
...@@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8) ...@@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
*/ */
return std::strchr(name_utf8, '/') == nullptr && return std::strchr(name_utf8, '/') == nullptr &&
#ifdef _WIN32
std::strchr(name_utf8, '\\') == nullptr &&
#endif
std::strchr(name_utf8, '\n') == nullptr && std::strchr(name_utf8, '\n') == nullptr &&
std::strchr(name_utf8, '\r') == nullptr; std::strchr(name_utf8, '\r') == nullptr;
} }
......
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
#include "storage/StorageState.hxx" #include "storage/StorageState.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "mixer/Volume.hxx"
#include "SongLoader.hxx" #include "SongLoader.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx" #include "Log.hxx"
...@@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config, ...@@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
void void
StateFile::RememberVersions() noexcept StateFile::RememberVersions() noexcept
{ {
prev_volume_version = sw_volume_state_get_hash(); prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash();
prev_output_version = audio_output_state_get_version(); prev_output_version = audio_output_state_get_version();
prev_playlist_version = playlist_state_get_hash(partition.playlist, prev_playlist_version = playlist_state_get_hash(partition.playlist,
partition.pc); partition.pc);
...@@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept ...@@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
bool bool
StateFile::IsModified() const noexcept StateFile::IsModified() const noexcept
{ {
return prev_volume_version != sw_volume_state_get_hash() || return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() ||
prev_output_version != audio_output_state_get_version() || prev_output_version != audio_output_state_get_version() ||
prev_playlist_version != playlist_state_get_hash(partition.playlist, prev_playlist_version != playlist_state_get_hash(partition.playlist,
partition.pc) partition.pc)
...@@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept ...@@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
inline void inline void
StateFile::Write(BufferedOutputStream &os) StateFile::Write(BufferedOutputStream &os)
{ {
save_sw_volume_state(os); partition.mixer_memento.SaveSoftwareVolumeState(os);
audio_output_state_save(os, partition.outputs); audio_output_state_save(os, partition.outputs);
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
...@@ -125,7 +124,7 @@ try { ...@@ -125,7 +124,7 @@ try {
const char *line; const char *line;
while ((line = file.ReadLine()) != nullptr) { while ((line = file.ReadLine()) != nullptr) {
success = read_sw_volume_state(line, partition.outputs) || success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) ||
audio_output_state_read(line, partition.outputs) || audio_output_state_read(line, partition.outputs) ||
playlist_state_restore(config, line, file, song_loader, playlist_state_restore(config, line, file, song_loader,
partition.playlist, partition.playlist,
......
...@@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config) ...@@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
{ {
#ifdef ANDROID #ifdef ANDROID
if (path.IsNull()) { if (path.IsNull()) {
const auto cache_dir = GetUserCacheDir(); const auto cache_dir = GetAppCacheDir();
if (cache_dir.IsNull()) if (cache_dir.IsNull())
return; return;
......
...@@ -26,19 +26,30 @@ ...@@ -26,19 +26,30 @@
#include "AudioManager.hxx" #include "AudioManager.hxx"
AllocatedPath static jmethodID getExternalFilesDir_method,
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept getCacheDir_method,
getSystemService_method;
void
Context::Initialise(JNIEnv *env) noexcept
{ {
assert(_type != nullptr); Java::Class cls{env, "android/content/Context"};
Java::Class cls{env, env->GetObjectClass(Get())}; getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
"(Ljava/lang/String;)Ljava/io/File;"); getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
assert(method); "()Ljava/io/File;");
getSystemService_method = env->GetMethodID(cls, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
}
Java::String type{env, _type}; AllocatedPath
Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept
{
assert(type != nullptr);
jobject file = env->CallObjectMethod(Get(), method, type.Get()); jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method,
Java::String::Optional(env, type).Get());
if (Java::DiscardException(env) || file == nullptr) if (Java::DiscardException(env) || file == nullptr)
return nullptr; return nullptr;
...@@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept ...@@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
{ {
assert(env != nullptr); assert(env != nullptr);
Java::Class cls(env, env->GetObjectClass(Get())); jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
jmethodID method = env->GetMethodID(cls, "getCacheDir",
"()Ljava/io/File;");
assert(method);
jobject file = env->CallObjectMethod(Get(), method);
if (Java::DiscardException(env) || file == nullptr) if (Java::DiscardException(env) || file == nullptr)
return nullptr; return nullptr;
...@@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept ...@@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
{ {
assert(env != nullptr); assert(env != nullptr);
Java::Class cls(env, env->GetObjectClass(Get()));
jmethodID method = env->GetMethodID(cls, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
assert(method);
Java::String name(env, "audio"); Java::String name(env, "audio");
jobject am = env->CallObjectMethod(Get(), method, name.Get()); jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
if (Java::DiscardException(env) || am == nullptr) if (Java::DiscardException(env) || am == nullptr)
return nullptr; return nullptr;
......
...@@ -27,12 +27,21 @@ class AudioManager; ...@@ -27,12 +27,21 @@ class AudioManager;
class Context : public Java::GlobalObject { class Context : public Java::GlobalObject {
public: public:
/**
* Global initialisation. Looks up the methods of the
* Context Java class.
*/
static void Initialise(JNIEnv *env) noexcept;
Context(JNIEnv *env, jobject obj) noexcept Context(JNIEnv *env, jobject obj) noexcept
:Java::GlobalObject(env, obj) {} :Java::GlobalObject(env, obj) {}
/**
* @param type the subdirectory name; may be nullptr
*/
[[gnu::pure]] [[gnu::pure]]
AllocatedPath GetExternalFilesDir(JNIEnv *env, AllocatedPath GetExternalFilesDir(JNIEnv *env,
const char *type) noexcept; const char *type=nullptr) noexcept;
[[gnu::pure]] [[gnu::pure]]
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
......
...@@ -25,13 +25,13 @@ ...@@ -25,13 +25,13 @@
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
namespace Environment { namespace Environment {
static Java::TrivialClass cls;
static jmethodID getExternalStorageDirectory_method; static Java::TrivialClass cls;
static jmethodID getExternalStoragePublicDirectory_method; static jmethodID getExternalStorageDirectory_method;
} static jmethodID getExternalStoragePublicDirectory_method;
void void
Environment::Initialise(JNIEnv *env) noexcept Initialise(JNIEnv *env) noexcept
{ {
cls.Find(env, "android/os/Environment"); cls.Find(env, "android/os/Environment");
...@@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept ...@@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
} }
void void
Environment::Deinitialise(JNIEnv *env) noexcept Deinitialise(JNIEnv *env) noexcept
{ {
cls.Clear(env); cls.Clear(env);
} }
AllocatedPath AllocatedPath
Environment::getExternalStorageDirectory() noexcept getExternalStorageDirectory(JNIEnv *env) noexcept
{ {
JNIEnv *env = Java::GetEnv();
jobject file = jobject file =
env->CallStaticObjectMethod(cls, env->CallStaticObjectMethod(cls,
getExternalStorageDirectory_method); getExternalStorageDirectory_method);
...@@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept ...@@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
} }
AllocatedPath AllocatedPath
Environment::getExternalStoragePublicDirectory(const char *type) noexcept getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
{ {
if (getExternalStoragePublicDirectory_method == nullptr) if (getExternalStoragePublicDirectory_method == nullptr)
/* needs API level 8 */ /* needs API level 8 */
return nullptr; return nullptr;
JNIEnv *env = Java::GetEnv();
Java::String type2(env, type); Java::String type2(env, type);
jobject file = env->CallStaticObjectMethod(Environment::cls, jobject file = env->CallStaticObjectMethod(cls,
Environment::getExternalStoragePublicDirectory_method, getExternalStoragePublicDirectory_method,
type2.Get()); type2.Get());
if (file == nullptr) if (file == nullptr)
return nullptr; return nullptr;
return Java::File::ToAbsolutePath(env, file); return Java::File::ToAbsolutePath(env, file);
} }
} // namespace Environment
...@@ -17,27 +17,29 @@ ...@@ -17,27 +17,29 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef MPD_ANDROID_ENVIRONMENT_HXX #pragma once
#define MPD_ANDROID_ENVIRONMENT_HXX
#include "util/Compiler.h"
#include <jni.h> #include <jni.h>
class AllocatedPath; class AllocatedPath;
namespace Environment { namespace Environment {
void Initialise(JNIEnv *env) noexcept;
void Deinitialise(JNIEnv *env) noexcept;
/** void
* Determine the mount point of the external SD card. Initialise(JNIEnv *env) noexcept;
*/
[[gnu::pure]] void
AllocatedPath getExternalStorageDirectory() noexcept; Deinitialise(JNIEnv *env) noexcept;
/**
* Determine the mount point of the external SD card.
*/
[[gnu::pure]]
AllocatedPath
getExternalStorageDirectory(JNIEnv *env) noexcept;
[[gnu::pure]] [[gnu::pure]]
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept; AllocatedPath
} getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
#endif } // namespace Environment
...@@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream { ...@@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream {
assert(fill <= data.size()); assert(fill <= data.size());
assert(position <= fill); assert(position <= fill);
return {&data[position], &data[fill]}; return {data.data() + position, data.data() + fill};
} }
void Consume(size_t nbytes) noexcept { void Consume(size_t nbytes) noexcept {
......
...@@ -22,6 +22,10 @@ if libzzip_dep.found() ...@@ -22,6 +22,10 @@ if libzzip_dep.found()
found_archive_plugin = true found_archive_plugin = true
endif endif
if not found_archive_plugin
subdir_done()
endif
archive_plugins = static_library( archive_plugins = static_library(
'archive_plugins', 'archive_plugins',
archive_plugins_sources, archive_plugins_sources,
......
...@@ -33,7 +33,6 @@ ...@@ -33,7 +33,6 @@
#include "TimePrint.hxx" #include "TimePrint.hxx"
#include "decoder/DecoderPrint.hxx" #include "decoder/DecoderPrint.hxx"
#include "ls.hxx" #include "ls.hxx"
#include "mixer/Volume.hxx"
#include "time/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
...@@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r) ...@@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
{ {
auto &partition = client.GetPartition(); auto &partition = client.GetPartition();
const auto volume = volume_level_get(partition.outputs); const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0) if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume); r.Fmt(FMT_STRING("volume: {}\n"), volume);
...@@ -337,7 +336,9 @@ handle_setvol(Client &client, Request args, Response &) ...@@ -337,7 +336,9 @@ handle_setvol(Client &client, Request args, Response &)
{ {
unsigned level = args.ParseUnsigned(0, 100); unsigned level = args.ParseUnsigned(0, 100);
volume_level_change(client.GetPartition().outputs, level); auto &partition = client.GetPartition();
partition.mixer_memento.SetVolume(partition.outputs, level);
partition.EmitIdle(IDLE_MIXER);
return CommandResult::OK; return CommandResult::OK;
} }
...@@ -346,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r) ...@@ -346,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
{ {
int relative = args.ParseInt(0, -100, 100); int relative = args.ParseInt(0, -100, 100);
auto &outputs = client.GetPartition().outputs; auto &partition = client.GetPartition();
auto &outputs = partition.outputs;
auto &mixer_memento = partition.mixer_memento;
const int old_volume = volume_level_get(outputs); const int old_volume = mixer_memento.GetVolume(outputs);
if (old_volume < 0) { if (old_volume < 0) {
r.Error(ACK_ERROR_SYSTEM, "No mixer"); r.Error(ACK_ERROR_SYSTEM, "No mixer");
return CommandResult::ERROR; return CommandResult::ERROR;
...@@ -360,8 +363,10 @@ handle_volume(Client &client, Request args, Response &r) ...@@ -360,8 +363,10 @@ handle_volume(Client &client, Request args, Response &r)
else if (new_volume > 100) else if (new_volume > 100)
new_volume = 100; new_volume = 100;
if (new_volume != old_volume) if (new_volume != old_volume) {
volume_level_change(outputs, new_volume); mixer_memento.SetVolume(outputs, new_volume);
partition.EmitIdle(IDLE_MIXER);
}
return CommandResult::OK; return CommandResult::OK;
} }
......
...@@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r) ...@@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
assert(args.size == 1); assert(args.size == 1);
unsigned device = args.ParseUnsigned(0); unsigned device = args.ParseUnsigned(0);
if (!audio_output_enable_index(client.GetPartition().outputs, device)) { auto &partition = client.GetPartition();
if (!audio_output_enable_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
...@@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r) ...@@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
assert(args.size == 1); assert(args.size == 1);
unsigned device = args.ParseUnsigned(0); unsigned device = args.ParseUnsigned(0);
if (!audio_output_disable_index(client.GetPartition().outputs, device)) { auto &partition = client.GetPartition();
if (!audio_output_disable_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
...@@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r) ...@@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
assert(args.size == 1); assert(args.size == 1);
unsigned device = args.ParseUnsigned(0); unsigned device = args.ParseUnsigned(0);
if (!audio_output_toggle_index(client.GetPartition().outputs, device)) { auto &partition = client.GetPartition();
if (!audio_output_toggle_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
......
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
#include "SingleMode.hxx" #include "SingleMode.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "client/Response.hxx" #include "client/Response.hxx"
#include "mixer/Volume.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "IdleFlags.hxx" #include "IdleFlags.hxx"
...@@ -131,7 +130,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r) ...@@ -131,7 +130,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
const auto &playlist = partition.playlist; const auto &playlist = partition.playlist;
const auto volume = volume_level_get(partition.outputs); const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0) if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume); r.Fmt(FMT_STRING("volume: {}\n"), volume);
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "config/Param.hxx" #include "config/Param.hxx"
#include "config/Block.hxx" #include "config/Block.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "fs/StandardDirectory.hxx" #include "fs/StandardDirectory.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
...@@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config, ...@@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
} else { } else {
/* if there is no override, use the cache directory */ /* if there is no override, use the cache directory */
const AllocatedPath cache_dir = GetUserCacheDir(); const AllocatedPath cache_dir = GetAppCacheDir();
if (cache_dir.IsNull()) if (cache_dir.IsNull())
return nullptr; return nullptr;
const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("mpd.db")); const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("db"));
auto db_file_utf8 = db_file.ToUTF8(); auto db_file_utf8 = db_file.ToUTF8();
if (db_file_utf8.empty()) if (db_file_utf8.empty())
return nullptr; return nullptr;
ConfigBlock block; ConfigBlock block;
block.AddBlockParam("path", std::move(db_file_utf8), -1); block.AddBlockParam("path", std::move(db_file_utf8), -1);
{
const auto mounts_dir = cache_dir
/ Path::FromFS(PATH_LITERAL("mounts"));
CreateDirectoryNoThrow(mounts_dir);
if (auto mounts_dir_utf8 = mounts_dir.ToUTF8();
!mounts_dir_utf8.empty())
block.AddBlockParam("cache_directory",
std::move(mounts_dir_utf8),
-1);
}
return DatabaseGlobalInit(main_event_loop, io_event_loop, return DatabaseGlobalInit(main_event_loop, io_event_loop,
listener, block); listener, block);
} }
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "lib/ffmpeg/Format.hxx" #include "lib/ffmpeg/Format.hxx"
#include "lib/ffmpeg/Codec.hxx" #include "lib/ffmpeg/Codec.hxx"
#include "lib/ffmpeg/SampleFormat.hxx" #include "lib/ffmpeg/SampleFormat.hxx"
#include "lib/ffmpeg/LibFmt.hxx"
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx" #include "FfmpegMetaData.hxx"
#include "FfmpegIo.hxx" #include "FfmpegIo.hxx"
...@@ -523,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input, ...@@ -523,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
return; return;
} }
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = codec_context->ch_layout.nb_channels;
#else
const unsigned channels = codec_context->channels;
#endif
const auto audio_format = CheckAudioFormat(codec_context->sample_rate, const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
sample_format, sample_format,
codec_context->channels); channels);
const SignedSongTime total_time = const SignedSongTime total_time =
av_stream.duration != (int64_t)AV_NOPTS_VALUE av_stream.duration != (int64_t)AV_NOPTS_VALUE
...@@ -635,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler) ...@@ -635,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
AV_TIME_BASE_Q)); AV_TIME_BASE_Q));
const auto &codec_params = *stream.codecpar; const auto &codec_params = *stream.codecpar;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = codec_params.ch_layout.nb_channels;
#else
const unsigned channels = codec_params.channels;
#endif
try { try {
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate, handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
ffmpeg_sample_format(AVSampleFormat(codec_params.format)), ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
codec_params.channels)); channels));
} catch (...) { } catch (...) {
} }
......
...@@ -21,10 +21,13 @@ ...@@ -21,10 +21,13 @@
#define __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS
#include "FfmpegIo.hxx" #include "FfmpegIo.hxx"
#include "libavutil/mem.h"
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
extern "C" {
#include <libavutil/mem.h>
}
AvioStream::~AvioStream() AvioStream::~AvioStream()
{ {
if (io != nullptr) { if (io != nullptr) {
......
...@@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder { ...@@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
FLAC__StreamEncoder *const fse; FLAC__StreamEncoder *const fse;
const unsigned compression; const unsigned compression;
const bool oggflac;
PcmBuffer expand_buffer; PcmBuffer expand_buffer;
...@@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block) ...@@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
} }
static void static void
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
const AudioFormat &audio_format) const AudioFormat &audio_format)
{ {
unsigned bits_per_sample; unsigned bits_per_sample;
...@@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, ...@@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
throw FormatRuntimeError("error setting flac sample rate to %d", throw FormatRuntimeError("error setting flac sample rate to %d",
audio_format.sample_rate); audio_format.sample_rate);
if (!FLAC__stream_encoder_set_ogg_serial_number(fse, if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
GenerateSerial())) GenerateSerial()))
throw FormatRuntimeError("error setting ogg serial number"); throw FormatRuntimeError("error setting ogg serial number");
} }
...@@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u ...@@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
:Encoder(_oggchaining), :Encoder(_oggchaining),
audio_format(_audio_format), fse(_fse), audio_format(_audio_format), fse(_fse),
compression(_compression), compression(_compression),
oggflac(_oggflac),
output_buffer(8192) output_buffer(8192)
{ {
/* this immediately outputs data through callback */ /* this immediately outputs data through callback */
auto init_status = _oggflac ? auto init_status = oggflac ?
FLAC__stream_encoder_init_ogg_stream(fse, FLAC__stream_encoder_init_ogg_stream(fse,
nullptr, WriteCallback, nullptr, WriteCallback,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
...@@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format) ...@@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
throw std::runtime_error("FLAC__stream_encoder_new() failed"); throw std::runtime_error("FLAC__stream_encoder_new() failed");
try { try {
flac_encoder_setup(fse, compression, audio_format); flac_encoder_setup(fse, compression, oggflac, audio_format);
} catch (...) { } catch (...) {
FLAC__stream_encoder_delete(fse); FLAC__stream_encoder_delete(fse);
throw; throw;
...@@ -222,7 +224,7 @@ void ...@@ -222,7 +224,7 @@ void
FlacEncoder::SendTag(const Tag &tag) FlacEncoder::SendTag(const Tag &tag)
{ {
/* re-initialize encoder since flac_encoder_finish resets everything */ /* re-initialize encoder since flac_encoder_finish resets everything */
flac_encoder_setup(fse, compression, audio_format); flac_encoder_setup(fse, compression, oggflac, audio_format);
FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
FLAC__StreamMetadata_VorbisComment_Entry entry; FLAC__StreamMetadata_VorbisComment_Entry entry;
......
...@@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format, ...@@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
buffer_sink(_buffer_sink), buffer_sink(_buffer_sink),
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)), in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
in_sample_rate(in_audio_format.sample_rate), in_sample_rate(in_audio_format.sample_rate),
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
in_channels(in_audio_format.channels), in_channels(in_audio_format.channels),
#endif
in_audio_frame_size(in_audio_format.GetFrameSize()), in_audio_frame_size(in_audio_format.GetFrameSize()),
out_audio_frame_size(_out_audio_format.GetFrameSize()) out_audio_frame_size(_out_audio_format.GetFrameSize())
{ {
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
#endif
} }
ConstBuffer<void> ConstBuffer<void>
...@@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src) ...@@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
frame.Unref(); frame.Unref();
frame->format = in_format; frame->format = in_format;
frame->sample_rate = in_sample_rate; frame->sample_rate = in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
frame->ch_layout = in_ch_layout;
#else
frame->channels = in_channels; frame->channels = in_channels;
#endif
frame->nb_samples = src.size / in_audio_frame_size; frame->nb_samples = src.size / in_audio_frame_size;
frame.GetBuffer(); frame.GetBuffer();
......
...@@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter { ...@@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
FfmpegBuffer interleave_buffer; FfmpegBuffer interleave_buffer;
const int in_format, in_sample_rate, in_channels; const int in_format, in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
AVChannelLayout in_ch_layout;
#else
const int in_channels;
#endif
const size_t in_audio_frame_size; const size_t in_audio_frame_size;
const size_t out_audio_frame_size; const size_t out_audio_frame_size;
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#include "ReplayGainInfo.hxx" #include "ReplayGainInfo.hxx"
#include "ReplayGainConfig.hxx" #include "ReplayGainConfig.hxx"
#include "mixer/MixerControl.hxx" #include "mixer/MixerControl.hxx"
#include "mixer/MixerInternal.hxx"
#include "mixer/Listener.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "pcm/Volume.hxx" #include "pcm/Volume.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
...@@ -171,9 +173,11 @@ ReplayGainFilter::Update() ...@@ -171,9 +173,11 @@ ReplayGainFilter::Update()
try { try {
mixer_set_volume(mixer, _volume); mixer_set_volume(mixer, _volume);
/* TODO: emit this idle event only for the /* invoke the mixer's listener manually, just
current partition */ in case the mixer implementation didn't do
idle_add(IDLE_MIXER); that already (this depends on the
implementation) */
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
} catch (...) { } catch (...) {
LogError(std::current_exception(), LogError(std::current_exception(),
"Failed to update hardware mixer"); "Failed to update hardware mixer");
......
...@@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true) ...@@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
#endif #endif
static inline bool
CreateDirectoryNoThrow(Path path) noexcept
{
#ifdef _WIN32
return CreateDirectory(path.c_str(), nullptr);
#else
return mkdir(path.c_str(), 0777);
#endif
}
/** /**
* Truncate a file that exists already. Throws std::system_error on * Truncate a file that exists already. Throws std::system_error on
* error. * error.
......
...@@ -53,6 +53,12 @@ ...@@ -53,6 +53,12 @@
#include "Main.hxx" #include "Main.hxx"
#endif #endif
#ifdef USE_XDG
#include "Version.h" // for PACKAGE_NAME
#define APP_FILENAME PATH_LITERAL(PACKAGE_NAME)
static constexpr Path app_filename = Path::FromFS(APP_FILENAME);
#endif
#if !defined(_WIN32) && !defined(ANDROID) #if !defined(_WIN32) && !defined(ANDROID)
class PasswdEntry class PasswdEntry
{ {
...@@ -254,7 +260,8 @@ GetUserMusicDir() noexcept ...@@ -254,7 +260,8 @@ GetUserMusicDir() noexcept
#elif defined(USE_XDG) #elif defined(USE_XDG)
return GetUserDir("XDG_MUSIC_DIR"); return GetUserDir("XDG_MUSIC_DIR");
#elif defined(ANDROID) #elif defined(ANDROID)
return Environment::getExternalStoragePublicDirectory("Music"); return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
"Music");
#else #else
return nullptr; return nullptr;
#endif #endif
...@@ -284,6 +291,24 @@ GetUserCacheDir() noexcept ...@@ -284,6 +291,24 @@ GetUserCacheDir() noexcept
} }
AllocatedPath AllocatedPath
GetAppCacheDir() noexcept
{
#ifdef USE_XDG
if (const auto user_dir = GetUserCacheDir(); !user_dir.IsNull()) {
auto dir = user_dir / app_filename;
CreateDirectoryNoThrow(dir);
return dir;
}
return nullptr;
#elif defined(ANDROID)
return context->GetCacheDir(Java::GetEnv());
#else
return nullptr;
#endif
}
AllocatedPath
GetUserRuntimeDir() noexcept GetUserRuntimeDir() noexcept
{ {
#ifdef USE_XDG #ifdef USE_XDG
...@@ -296,7 +321,7 @@ GetUserRuntimeDir() noexcept ...@@ -296,7 +321,7 @@ GetUserRuntimeDir() noexcept
AllocatedPath AllocatedPath
GetAppRuntimeDir() noexcept GetAppRuntimeDir() noexcept
{ {
#ifdef __linux__ #if defined(__linux__) && !defined(ANDROID)
/* systemd specific; see systemd.exec(5) */ /* systemd specific; see systemd.exec(5) */
if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY")) if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
if (auto dir = StringView{runtime_directory}.Split(':').first; if (auto dir = StringView{runtime_directory}.Split(':').first;
...@@ -306,8 +331,8 @@ GetAppRuntimeDir() noexcept ...@@ -306,8 +331,8 @@ GetAppRuntimeDir() noexcept
#ifdef USE_XDG #ifdef USE_XDG
if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) { if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
auto dir = user_dir / Path::FromFS("mpd"); auto dir = user_dir / app_filename;
mkdir(dir.c_str(), 0700); CreateDirectoryNoThrow(dir);
return dir; return dir;
} }
#endif #endif
......
...@@ -44,6 +44,13 @@ AllocatedPath ...@@ -44,6 +44,13 @@ AllocatedPath
GetUserCacheDir() noexcept; GetUserCacheDir() noexcept;
/** /**
* Obtains cache directory for this application.
*/
[[gnu::const]]
AllocatedPath
GetAppCacheDir() noexcept;
/**
* Obtains the runtime directory for the current user. * Obtains the runtime directory for the current user.
*/ */
[[gnu::const]] [[gnu::const]]
......
...@@ -45,6 +45,14 @@ ...@@ -45,6 +45,14 @@
#include <cdio/cd_types.h> #include <cdio/cd_types.h>
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static unsigned speed = 0;
/* Default to full paranoia, but allow skipping sectors. */
static int mode_flags = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;
class CdioParanoiaInputStream final : public InputStream { class CdioParanoiaInputStream final : public InputStream {
cdrom_drive_t *const drv; cdrom_drive_t *const drv;
CdIo_t *const cdio; CdIo_t *const cdio;
...@@ -65,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream { ...@@ -65,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream {
lsn_from(_lsn_from), lsn_from(_lsn_from),
buffer_lsn(-1) buffer_lsn(-1)
{ {
/* Set reading mode for full paranoia, but allow para.SetMode(mode_flags);
skipping sectors. */
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
/* seek to beginning of the track */ /* seek to beginning of the track */
para.Seek(lsn_from); para.Seek(lsn_from);
...@@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream { ...@@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override; void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
}; };
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static unsigned speed = 0;
static void static void
input_cdio_init(EventLoop &, const ConfigBlock &block) input_cdio_init(EventLoop &, const ConfigBlock &block)
{ {
...@@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block) ...@@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
value); value);
} }
speed = block.GetBlockValue("speed",0U); speed = block.GetBlockValue("speed",0U);
if (const auto *param = block.GetBlockParam("mode")) {
param->With([](const char *s){
if (StringIsEqual(s, "disable"))
mode_flags = PARANOIA_MODE_DISABLE;
else if (StringIsEqual(s, "overlap"))
mode_flags = PARANOIA_MODE_OVERLAP;
else if (StringIsEqual(s, "full"))
mode_flags = PARANOIA_MODE_FULL;
else
throw std::invalid_argument{"Invalid paranoia mode"};
});
}
if (const auto *param = block.GetBlockParam("skip")) {
if (param->GetBoolValue())
mode_flags &= ~PARANOIA_MODE_NEVERSKIP;
else
mode_flags |= PARANOIA_MODE_NEVERSKIP;
}
} }
struct CdioUri { struct CdioUri {
......
...@@ -439,6 +439,14 @@ CurlInputStream::InitEasy() ...@@ -439,6 +439,14 @@ CurlInputStream::InitEasy()
request->SetVerifyPeer(verify_peer); request->SetVerifyPeer(verify_peer);
request->SetVerifyHost(verify_host); request->SetVerifyHost(verify_host);
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get()); request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
try {
request->SetProxyVerifyPeer(verify_peer);
request->SetProxyVerifyHost(verify_host);
} catch (...) {
/* these methods fail if libCURL was compiled with
CURL_DISABLE_PROXY; ignore silently */
}
} }
void void
......
...@@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept ...@@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept
AllocatedPath AllocatedPath
Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
{ {
assert(env != nullptr);
assert(_file != nullptr);
LocalObject file(env, _file); LocalObject file(env, _file);
const jstring path = GetAbsolutePath(env, file); const jstring path = GetAbsolutePath(env, file);
......
...@@ -89,6 +89,16 @@ public: ...@@ -89,6 +89,16 @@ public:
String(JNIEnv *_env, const char *_value) noexcept String(JNIEnv *_env, const char *_value) noexcept
:LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {} :LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {}
/**
* This constructor allows passing a nullptr value, which maps
* to a "null" in Java.
*/
static String Optional(JNIEnv *_env, const char *_value) noexcept {
return _value != nullptr
? String{_env, _value}
: String{};
}
static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept { static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept {
return {env, s, env->GetStringUTFChars(s, nullptr)}; return {env, s, env->GetStringUTFChars(s, nullptr)};
} }
......
...@@ -123,6 +123,14 @@ public: ...@@ -123,6 +123,14 @@ public:
easy.SetVerifyPeer(value); easy.SetVerifyPeer(value);
} }
void SetProxyVerifyHost(bool value) {
easy.SetOption(CURLOPT_PROXY_SSL_VERIFYHOST, value ? 2L : 0L);
}
void SetProxyVerifyPeer(bool value) {
easy.SetOption(CURLOPT_PROXY_SSL_VERIFYPEER, value);
}
void SetNoBody(bool value=true) { void SetNoBody(bool value=true) {
easy.SetNoBody(value); easy.SetNoBody(value);
} }
......
...@@ -48,7 +48,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format, ...@@ -48,7 +48,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
Frame frame; Frame frame;
frame->format = ToFfmpegSampleFormat(in_audio_format.format); frame->format = ToFfmpegSampleFormat(in_audio_format.format);
frame->sample_rate = in_audio_format.sample_rate; frame->sample_rate = in_audio_format.sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
av_channel_layout_default(&frame->ch_layout, in_audio_format.channels);
#else
frame->channels = in_audio_format.channels; frame->channels = in_audio_format.channels;
#endif
frame->nb_samples = 1; frame->nb_samples = 1;
frame.GetBuffer(); frame.GetBuffer();
...@@ -75,8 +79,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format, ...@@ -75,8 +79,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
if (sample_format == SampleFormat::UNDEFINED) if (sample_format == SampleFormat::UNDEFINED)
throw std::runtime_error("Unsupported FFmpeg sample format"); throw std::runtime_error("Unsupported FFmpeg sample format");
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned out_channels = frame->ch_layout.nb_channels;
#else
const unsigned out_channels = frame->channels;
#endif
return CheckAudioFormat(frame->sample_rate, sample_format, return CheckAudioFormat(frame->sample_rate, sample_format,
frame->channels); out_channels);
} }
} // namespace Ffmpeg } // namespace Ffmpeg
...@@ -38,7 +38,11 @@ InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer) ...@@ -38,7 +38,11 @@ InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer)
assert(frame.nb_samples > 0); assert(frame.nb_samples > 0);
const auto format = AVSampleFormat(frame.format); const auto format = AVSampleFormat(frame.format);
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = frame.ch_layout.nb_channels;
#else
const unsigned channels = frame.channels; const unsigned channels = frame.channels;
#endif
const std::size_t n_frames = frame.nb_samples; const std::size_t n_frames = frame.nb_samples;
int plane_size; int plane_size;
......
/*
* Copyright 2003-2022 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
extern "C" {
#include <libavutil/samplefmt.h>
}
#include <fmt/format.h>
template<>
struct fmt::formatter<AVSampleFormat> : formatter<string_view>
{
template<typename FormatContext>
auto format(const AVSampleFormat format, FormatContext &ctx) {
const char *name = av_get_sample_fmt_name(format);
if (name == nullptr)
name = "?";
return formatter<string_view>::format(name, ctx);
}
};
...@@ -18,17 +18,25 @@ if icu_dep.found() ...@@ -18,17 +18,25 @@ if icu_dep.found()
'Util.cxx', 'Util.cxx',
'Init.cxx', 'Init.cxx',
] ]
elif not get_option('iconv').disabled() else
# an installed iconv library will make the builtin iconv() unavailable, if meson.version().version_compare('>= 0.60')
# so search for the library first and pass it as (possible) dependency iconv_dep = dependency('iconv', required: get_option('iconv'))
iconv_dep = compiler.find_library('libiconv', required: false) conf.set('HAVE_ICONV', iconv_dep.found())
have_iconv = compiler.has_function('iconv', elif not get_option('iconv').disabled()
dependencies: iconv_dep, iconv_open_snippet = '''#include <iconv.h>
prefix : '#include <iconv.h>') int main() {
if not have_iconv and get_option('iconv').enabled() iconv_open("","");
error('iconv() not available') }'''
have_iconv = compiler.links(iconv_open_snippet, name: 'iconv_open')
if not have_iconv
iconv_dep = compiler.find_library('iconv', required: false)
have_iconv = compiler.links(iconv_open_snippet, dependencies: iconv_dep, name: 'iconv_open')
endif
if not have_iconv and get_option('iconv').enabled()
error('iconv() not available')
endif
conf.set('HAVE_ICONV', have_iconv)
endif endif
conf.set('HAVE_ICONV', have_iconv)
endif endif
icu = static_library( icu = static_library(
......
...@@ -17,14 +17,11 @@ ...@@ -17,14 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "Volume.hxx" #include "Memento.hxx"
#include "output/MultipleOutputs.hxx" #include "output/MultipleOutputs.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/Domain.hxx"
#include "system/PeriodClock.hxx"
#include "io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "Log.hxx"
#include <cassert> #include <cassert>
...@@ -32,24 +29,8 @@ ...@@ -32,24 +29,8 @@
#define SW_VOLUME_STATE "sw_volume: " #define SW_VOLUME_STATE "sw_volume: "
static constexpr Domain volume_domain("volume");
static unsigned volume_software_set = 100;
/** the cached hardware mixer value; invalid if negative */
static int last_hardware_volume = -1;
/** the age of #last_hardware_volume */
static PeriodClock hardware_volume_clock;
void
InvalidateHardwareVolume() noexcept
{
/* flush the hardware volume cache */
last_hardware_volume = -1;
}
int int
volume_level_get(const MultipleOutputs &outputs) noexcept MixerMemento::GetVolume(const MultipleOutputs &outputs) noexcept
{ {
if (last_hardware_volume >= 0 && if (last_hardware_volume >= 0 &&
!hardware_volume_clock.CheckUpdate(std::chrono::seconds(1))) !hardware_volume_clock.CheckUpdate(std::chrono::seconds(1)))
...@@ -60,8 +41,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept ...@@ -60,8 +41,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept
return last_hardware_volume; return last_hardware_volume;
} }
static bool inline bool
software_volume_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume)
{ {
assert(volume <= 100); assert(volume <= 100);
...@@ -71,8 +52,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume) ...@@ -71,8 +52,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
return true; return true;
} }
static void inline void
hardware_volume_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetHardwareVolume(MultipleOutputs &outputs, unsigned volume)
{ {
/* reset the cache */ /* reset the cache */
last_hardware_volume = -1; last_hardware_volume = -1;
...@@ -81,19 +62,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume) ...@@ -81,19 +62,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
} }
void void
volume_level_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume)
{ {
assert(volume <= 100); assert(volume <= 100);
volume_software_set = volume; volume_software_set = volume;
idle_add(IDLE_MIXER); SetHardwareVolume(outputs, volume);
hardware_volume_change(outputs, volume);
} }
bool bool
read_sw_volume_state(const char *line, MultipleOutputs &outputs) MixerMemento::LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs)
{ {
char *end = nullptr; char *end = nullptr;
long int sv; long int sv;
...@@ -104,21 +83,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs) ...@@ -104,21 +83,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs)
sv = strtol(line, &end, 10); sv = strtol(line, &end, 10);
if (*end == 0 && sv >= 0 && sv <= 100) if (*end == 0 && sv >= 0 && sv <= 100)
software_volume_change(outputs, sv); SetSoftwareVolume(outputs, sv);
else
FmtWarning(volume_domain,
"Can't parse software volume: {}", line);
return true; return true;
} }
void void
save_sw_volume_state(BufferedOutputStream &os) MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const
{ {
os.Format(SW_VOLUME_STATE "%u\n", volume_software_set); os.Format(SW_VOLUME_STATE "%u\n", volume_software_set);
} }
unsigned
sw_volume_state_get_hash() noexcept
{
return volume_software_set;
}
...@@ -17,39 +17,59 @@ ...@@ -17,39 +17,59 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef MPD_VOLUME_HXX #pragma once
#define MPD_VOLUME_HXX
#include "system/PeriodClock.hxx"
class MultipleOutputs; class MultipleOutputs;
class BufferedOutputStream; class BufferedOutputStream;
void
InvalidateHardwareVolume() noexcept;
[[gnu::pure]]
int
volume_level_get(const MultipleOutputs &outputs) noexcept;
/** /**
* Throws on error. * Cache for hardware/software volume levels.
*/ */
void class MixerMemento {
volume_level_change(MultipleOutputs &outputs, unsigned volume); unsigned volume_software_set = 100;
bool /** the cached hardware mixer value; invalid if negative */
read_sw_volume_state(const char *line, MultipleOutputs &outputs); int last_hardware_volume = -1;
void /** the age of #last_hardware_volume */
save_sw_volume_state(BufferedOutputStream &os); PeriodClock hardware_volume_clock;
/** public:
* Generates a hash number for the current state of the software /**
* volume control. This is used by timer_save_state_file() to * Flush the hardware volume cache.
* determine whether the state has changed and the state file should */
* be saved. void InvalidateHardwareVolume() noexcept {
*/ last_hardware_volume = -1;
[[gnu::pure]] }
unsigned
sw_volume_state_get_hash() noexcept; [[gnu::pure]]
int GetVolume(const MultipleOutputs &outputs) noexcept;
/**
* Throws on error.
*
* Note: the caller is responsible for emitting #IDLE_MIXER.
*/
void SetVolume(MultipleOutputs &outputs, unsigned volume);
bool LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs);
void SaveSoftwareVolumeState(BufferedOutputStream &os) const;
/**
* Generates a hash number for the current state of the software
* volume control. This is used by timer_save_state_file() to
* determine whether the state has changed and the state file should
* be saved.
*/
[[gnu::pure]]
unsigned GetSoftwareVolumeStateHash() const noexcept {
return volume_software_set;
}
#endif private:
bool SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume);
void SetHardwareVolume(MultipleOutputs &outputs, unsigned volume);
};
...@@ -188,8 +188,8 @@ MultipleOutputs::SetSoftwareVolume(unsigned volume) noexcept ...@@ -188,8 +188,8 @@ MultipleOutputs::SetSoftwareVolume(unsigned volume) noexcept
auto *mixer = ao->GetMixer(); auto *mixer = ao->GetMixer();
if (mixer != nullptr && if (mixer != nullptr &&
(&mixer->plugin == &software_mixer_plugin || (mixer->IsPlugin(software_mixer_plugin) ||
&mixer->plugin == &null_mixer_plugin)) mixer->IsPlugin(null_mixer_plugin)))
mixer_set_volume(mixer, volume); mixer_set_volume(mixer, volume);
} }
} }
...@@ -92,7 +92,7 @@ mixer_close(Mixer *mixer) ...@@ -92,7 +92,7 @@ mixer_close(Mixer *mixer)
void void
mixer_auto_close(Mixer *mixer) mixer_auto_close(Mixer *mixer)
{ {
if (!mixer->plugin.global) if (!mixer->IsGlobal())
mixer_close(mixer); mixer_close(mixer);
} }
...@@ -103,7 +103,7 @@ mixer_get_volume(Mixer *mixer) ...@@ -103,7 +103,7 @@ mixer_get_volume(Mixer *mixer)
assert(mixer != nullptr); assert(mixer != nullptr);
if (mixer->plugin.global && !mixer->failure) if (mixer->IsGlobal() && !mixer->failure)
mixer_open(mixer); mixer_open(mixer);
const std::scoped_lock<Mutex> protect(mixer->mutex); const std::scoped_lock<Mutex> protect(mixer->mutex);
...@@ -128,7 +128,7 @@ mixer_set_volume(Mixer *mixer, unsigned volume) ...@@ -128,7 +128,7 @@ mixer_set_volume(Mixer *mixer, unsigned volume)
assert(mixer != nullptr); assert(mixer != nullptr);
assert(volume <= 100); assert(volume <= 100);
if (mixer->plugin.global && !mixer->failure) if (mixer->IsGlobal() && !mixer->failure)
mixer_open(mixer); mixer_open(mixer);
const std::scoped_lock<Mutex> protect(mixer->mutex); const std::scoped_lock<Mutex> protect(mixer->mutex);
......
...@@ -28,13 +28,15 @@ ...@@ -28,13 +28,15 @@
#include "MultipleOutputs.hxx" #include "MultipleOutputs.hxx"
#include "Client.hxx" #include "Client.hxx"
#include "mixer/MixerControl.hxx" #include "mixer/MixerControl.hxx"
#include "mixer/Volume.hxx" #include "mixer/Memento.hxx"
#include "Idle.hxx" #include "Idle.hxx"
extern unsigned audio_output_state_version; extern unsigned audio_output_state_version;
bool bool
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) audio_output_enable_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx)
{ {
if (idx >= outputs.Size()) if (idx >= outputs.Size())
return false; return false;
...@@ -46,7 +48,7 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) ...@@ -46,7 +48,7 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
idle_add(IDLE_OUTPUT); idle_add(IDLE_OUTPUT);
if (ao.GetMixer() != nullptr) { if (ao.GetMixer() != nullptr) {
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
...@@ -58,7 +60,9 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) ...@@ -58,7 +60,9 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
} }
bool bool
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) audio_output_disable_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx)
{ {
if (idx >= outputs.Size()) if (idx >= outputs.Size())
return false; return false;
...@@ -72,7 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) ...@@ -72,7 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
auto *mixer = ao.GetMixer(); auto *mixer = ao.GetMixer();
if (mixer != nullptr) { if (mixer != nullptr) {
mixer_close(mixer); mixer_close(mixer);
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
...@@ -84,7 +88,9 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) ...@@ -84,7 +88,9 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
} }
bool bool
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx) audio_output_toggle_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx)
{ {
if (idx >= outputs.Size()) if (idx >= outputs.Size())
return false; return false;
...@@ -97,7 +103,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx) ...@@ -97,7 +103,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
auto *mixer = ao.GetMixer(); auto *mixer = ao.GetMixer();
if (mixer != nullptr) { if (mixer != nullptr) {
mixer_close(mixer); mixer_close(mixer);
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
} }
......
...@@ -28,26 +28,33 @@ ...@@ -28,26 +28,33 @@
#define MPD_OUTPUT_COMMAND_HXX #define MPD_OUTPUT_COMMAND_HXX
class MultipleOutputs; class MultipleOutputs;
class MixerMemento;
/** /**
* Enables an audio output. Returns false if the specified output * Enables an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool bool
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx); audio_output_enable_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx);
/** /**
* Disables an audio output. Returns false if the specified output * Disables an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool bool
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx); audio_output_disable_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx);
/** /**
* Toggles an audio output. Returns false if the specified output * Toggles an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool bool
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx); audio_output_toggle_index(MultipleOutputs &outputs,
MixerMemento &mixer_memento,
unsigned idx);
#endif #endif
...@@ -812,8 +812,12 @@ AlsaOutput::Open(AudioFormat &audio_format) ...@@ -812,8 +812,12 @@ AlsaOutput::Open(AudioFormat &audio_format)
fmt::format("Failed to open ALSA device \"{}\"", fmt::format("Failed to open ALSA device \"{}\"",
GetDevice()).c_str()); GetDevice()).c_str());
const char *pcm_name = snd_pcm_name(pcm);
if (pcm_name == nullptr)
pcm_name = "?";
FmtDebug(alsa_output_domain, "opened {} type={}", FmtDebug(alsa_output_domain, "opened {} type={}",
snd_pcm_name(pcm), pcm_name,
snd_pcm_type_name(snd_pcm_type(pcm))); snd_pcm_type_name(snd_pcm_type(pcm)));
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
......
...@@ -47,6 +47,15 @@ ...@@ -47,6 +47,15 @@
#include <memory> #include <memory>
// Backward compatibility from OSX 12.0 API change
#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000)
#define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMain
#define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMainVolume
#else
#define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMaster
#define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMasterVolume
#endif
static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100; static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100;
static StringBuffer<64> static StringBuffer<64>
...@@ -160,13 +169,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block) ...@@ -160,13 +169,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block)
static constexpr AudioObjectPropertyAddress default_system_output_device{ static constexpr AudioObjectPropertyAddress default_system_output_device{
kAudioHardwarePropertyDefaultSystemOutputDevice, kAudioHardwarePropertyDefaultSystemOutputDevice,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
static constexpr AudioObjectPropertyAddress default_output_device{ static constexpr AudioObjectPropertyAddress default_output_device{
kAudioHardwarePropertyDefaultOutputDevice, kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
const auto &aopa = const auto &aopa =
...@@ -195,9 +204,9 @@ int ...@@ -195,9 +204,9 @@ int
OSXOutput::GetVolume() OSXOutput::GetVolume()
{ {
static constexpr AudioObjectPropertyAddress aopa = { static constexpr AudioObjectPropertyAddress aopa = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume, KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id, const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
...@@ -211,9 +220,9 @@ OSXOutput::SetVolume(unsigned new_volume) ...@@ -211,9 +220,9 @@ OSXOutput::SetVolume(unsigned new_volume)
{ {
Float32 vol = new_volume / 100.0; Float32 vol = new_volume / 100.0;
static constexpr AudioObjectPropertyAddress aopa = { static constexpr AudioObjectPropertyAddress aopa = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume, KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
UInt32 size = sizeof(vol); UInt32 size = sizeof(vol);
OSStatus status = AudioObjectSetPropertyData(dev_id, OSStatus status = AudioObjectSetPropertyData(dev_id,
...@@ -366,25 +375,25 @@ osx_output_set_device_format(AudioDeviceID dev_id, ...@@ -366,25 +375,25 @@ osx_output_set_device_format(AudioDeviceID dev_id,
static constexpr AudioObjectPropertyAddress aopa_device_streams = { static constexpr AudioObjectPropertyAddress aopa_device_streams = {
kAudioDevicePropertyStreams, kAudioDevicePropertyStreams,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
static constexpr AudioObjectPropertyAddress aopa_stream_direction = { static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
kAudioStreamPropertyDirection, kAudioStreamPropertyDirection,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = { static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
kAudioStreamPropertyAvailablePhysicalFormats, kAudioStreamPropertyAvailablePhysicalFormats,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = { static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
kAudioStreamPropertyPhysicalFormat, kAudioStreamPropertyPhysicalFormat,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
OSStatus err; OSStatus err;
...@@ -484,7 +493,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept ...@@ -484,7 +493,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept
static constexpr AudioObjectPropertyAddress aopa = { static constexpr AudioObjectPropertyAddress aopa = {
kAudioDevicePropertyHogMode, kAudioDevicePropertyHogMode,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
}; };
pid_t hog_pid; pid_t hog_pid;
...@@ -538,7 +547,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept ...@@ -538,7 +547,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
static constexpr AudioObjectPropertyAddress aopa_name{ static constexpr AudioObjectPropertyAddress aopa_name{
kAudioObjectPropertyName, kAudioObjectPropertyName,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
char actual_name[256]; char actual_name[256];
...@@ -561,7 +570,7 @@ FindAudioDeviceByName(const char *name) ...@@ -561,7 +570,7 @@ FindAudioDeviceByName(const char *name)
static constexpr AudioObjectPropertyAddress aopa_hw_devices{ static constexpr AudioObjectPropertyAddress aopa_hw_devices{
kAudioHardwarePropertyDevices, kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain, KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
}; };
const auto ids = const auto ids =
......
...@@ -514,6 +514,7 @@ PipeWireOutput::Open(AudioFormat &audio_format) ...@@ -514,6 +514,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_CATEGORY, "Playback",
PW_KEY_MEDIA_ROLE, "Music", PW_KEY_MEDIA_ROLE, "Music",
PW_KEY_APP_NAME, "Music Player Daemon", PW_KEY_APP_NAME, "Music Player Daemon",
PW_KEY_APP_ICON_NAME, "mpd",
nullptr); nullptr);
pw_properties_setf(props, PW_KEY_NODE_NAME, "mpd.%s", name); pw_properties_setf(props, PW_KEY_NODE_NAME, "mpd.%s", name);
......
...@@ -17,7 +17,6 @@ storage_glue = static_library( ...@@ -17,7 +17,6 @@ storage_glue = static_library(
'CompositeStorage.cxx', 'CompositeStorage.cxx',
'MemoryDirectoryReader.cxx', 'MemoryDirectoryReader.cxx',
'Configured.cxx', 'Configured.cxx',
'StorageState.cxx',
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
boost_dep, boost_dep,
...@@ -31,4 +30,3 @@ storage_glue_dep = declare_dependency( ...@@ -31,4 +30,3 @@ storage_glue_dep = declare_dependency(
storage_plugins_dep, storage_plugins_dep,
], ],
) )
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
#include "event/InjectEvent.hxx" #include "event/InjectEvent.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "time/Parser.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
...@@ -171,8 +170,9 @@ struct DavResponse { ...@@ -171,8 +170,9 @@ struct DavResponse {
} }
}; };
[[gnu::pure]]
static unsigned static unsigned
ParseStatus(const char *s) ParseStatus(const char *s) noexcept
{ {
/* skip the "HTTP/1.1" prefix */ /* skip the "HTTP/1.1" prefix */
const char *space = std::strchr(s, ' '); const char *space = std::strchr(s, ' ');
...@@ -182,37 +182,37 @@ ParseStatus(const char *s) ...@@ -182,37 +182,37 @@ ParseStatus(const char *s)
return strtoul(space + 1, nullptr, 10); return strtoul(space + 1, nullptr, 10);
} }
[[gnu::pure]]
static unsigned static unsigned
ParseStatus(const char *s, size_t length) ParseStatus(const char *s, size_t length) noexcept
{ {
return ParseStatus(std::string(s, length).c_str()); return ParseStatus(std::string(s, length).c_str());
} }
[[gnu::pure]]
static std::chrono::system_clock::time_point static std::chrono::system_clock::time_point
ParseTimeStamp(const char *s) ParseTimeStamp(const char *s) noexcept
{ {
try { return std::chrono::system_clock::from_time_t(curl_getdate(s, nullptr));
// TODO: make this more robust
return ParseTimePoint(s, "%a, %d %b %Y %T");
} catch (...) {
return std::chrono::system_clock::time_point::min();
}
} }
[[gnu::pure]]
static std::chrono::system_clock::time_point static std::chrono::system_clock::time_point
ParseTimeStamp(const char *s, size_t length) ParseTimeStamp(const char *s, size_t length) noexcept
{ {
return ParseTimeStamp(std::string(s, length).c_str()); return ParseTimeStamp(std::string(s, length).c_str());
} }
[[gnu::pure]]
static uint64_t static uint64_t
ParseU64(const char *s) ParseU64(const char *s) noexcept
{ {
return strtoull(s, nullptr, 10); return strtoull(s, nullptr, 10);
} }
[[gnu::pure]]
static uint64_t static uint64_t
ParseU64(const char *s, size_t length) ParseU64(const char *s, size_t length) noexcept
{ {
return ParseU64(std::string(s, length).c_str()); return ParseU64(std::string(s, length).c_str());
} }
...@@ -278,6 +278,7 @@ public: ...@@ -278,6 +278,7 @@ public:
"<a:resourcetype/>" "<a:resourcetype/>"
"<a:getcontenttype/>" "<a:getcontenttype/>"
"<a:getcontentlength/>" "<a:getcontentlength/>"
"<a:getlastmodified/>"
"</a:prop>" "</a:prop>"
"</a:propfind>"); "</a:propfind>");
} }
......
...@@ -70,8 +70,11 @@ FormatLastError(DWORD code, const char *fmt, Args&&... args) noexcept ...@@ -70,8 +70,11 @@ FormatLastError(DWORD code, const char *fmt, Args&&... args) noexcept
{ {
char buffer[512]; char buffer[512];
const auto end = buffer + sizeof(buffer); const auto end = buffer + sizeof(buffer);
size_t length = snprintf(buffer, sizeof(buffer) - 128, constexpr std::size_t max_prefix = sizeof(buffer) - 128;
size_t length = snprintf(buffer, max_prefix,
fmt, std::forward<Args>(args)...); fmt, std::forward<Args>(args)...);
if (length >= max_prefix)
length = max_prefix - 1;
char *p = buffer + length; char *p = buffer + length;
*p++ = ':'; *p++ = ':';
*p++ = ' '; *p++ = ' ';
......
/*
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - 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 "Parser.hxx"
#include "Convert.hxx"
#include <cassert>
#include <stdexcept>
#include <time.h>
std::chrono::system_clock::time_point
ParseTimePoint(const char *s, const char *format)
{
assert(s != nullptr);
assert(format != nullptr);
#ifdef _WIN32
/* TODO: emulate strptime()? */
(void)s;
(void)format;
throw std::runtime_error("Time parsing not implemented on Windows");
#else
struct tm tm{};
const char *end = strptime(s, format, &tm);
if (end == nullptr || *end != 0)
throw std::runtime_error("Failed to parse time stamp");
return TimeGm(tm);
#endif /* !_WIN32 */
}
/*
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - 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 TIME_PARSER_HXX
#define TIME_PARSER_HXX
#include <chrono>
/**
* Parse a time stamp.
*
* Throws std::runtime_error on error.
*/
std::chrono::system_clock::time_point
ParseTimePoint(const char *s, const char *format);
#endif
time = static_library( time = static_library(
'time', 'time',
'Parser.cxx',
'Convert.cxx', 'Convert.cxx',
'ISO8601.cxx', 'ISO8601.cxx',
'Math.cxx', 'Math.cxx',
......
[wrap-file] [wrap-file]
directory = expat-2.4.8 directory = expat-2.5.0
source_url = https://github.com/libexpat/libexpat/releases/download/R_2_4_8/expat-2.4.8.tar.xz source_url = https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.xz
source_filename = expat-2.4.8.tar.bz2 source_filename = expat-2.5.0.tar.bz2
source_hash = f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25 source_hash = ef2420f0232c087801abf705e89ae65f6257df6b7931d37846a193ef2e8cdcbe
patch_filename = expat_2.4.8-1_patch.zip patch_filename = expat_2.5.0-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.4.8-1/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.5.0-1/get_patch
patch_hash = 9aec253a2c6d1c0feb852c5c6920298d14701eeec7acc6832bb402438b52112a patch_hash = 0d0d6e07ed21cf4892126a8270f5fd182012ab34b3ebe24932a2bef5ca608a61
wrapdb_version = 2.5.0-1
[provide] [provide]
expat = expat_dep expat = expat_dep
[wrap-file] [wrap-file]
directory = fmt-8.1.1 directory = fmt-9.1.0
source_url = https://github.com/fmtlib/fmt/archive/8.1.1.tar.gz source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz
source_filename = fmt-8.1.1.tar.gz source_filename = fmt-9.1.0.tar.gz
source_hash = 3d794d3cf67633b34b2771eb9f073bde87e846e0d395d254df7b211ef1ec7346 source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2
patch_filename = fmt_8.1.1-2_patch.zip patch_filename = fmt_9.1.0-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_8.1.1-2/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-1/get_patch
patch_hash = cd001046281330a8862591780a9ea71a1fa594edd0d015deb24e44680c9ea33b patch_hash = 4557b9ba87b3eb63694ed9b21d1a2117d4a97ca56b91085b10288e9a5294adf8
wrapdb_version = 8.1.1-2 wrapdb_version = 9.1.0-1
[provide] [provide]
fmt = fmt_dep fmt = fmt_dep
[wrap-file] [wrap-file]
directory = sqlite-amalgamation-3380000 directory = sqlite-amalgamation-3390300
source_url = https://sqlite.org/2022/sqlite-amalgamation-3380000.zip source_url = https://sqlite.org/2022/sqlite-amalgamation-3390300.zip
source_filename = sqlite-amalgamation-3380000.zip source_filename = sqlite-amalgamation-3390300.zip
source_hash = e055f6054e97747a135c89e36520c0a423249e8a91c5fc445163f4a6adb20df6 source_hash = a89db3030d229d860ae56a8bac50ac9761434047ae886e47e7c8f9f428fa98ad
patch_filename = sqlite3_3.38.0-1_patch.zip patch_filename = sqlite3_3.39.3-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.38.0-1/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.39.3-1/get_patch
patch_hash = 49e30bf010ff63ab772d5417885e6905379025ceac80382e292c6dbd3a9da744 patch_hash = f5c41ff7b3da1108ed221b9a820b41188550cafb8a6c3d247bb40bd598775050
wrapdb_version = 3.39.3-1
[provide] [provide]
sqlite3 = sqlite3_dep sqlite3 = sqlite3_dep
...@@ -3,13 +3,12 @@ directory = libvorbis-1.3.7 ...@@ -3,13 +3,12 @@ directory = libvorbis-1.3.7
source_url = https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz source_url = https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz
source_filename = libvorbis-1.3.7.tar.xz source_filename = libvorbis-1.3.7.tar.xz
source_hash = b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b source_hash = b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b
patch_filename = vorbis_1.3.7-3_patch.zip patch_filename = vorbis_1.3.7-4_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-3/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-4/get_patch
patch_hash = 6cb90a61ede8c64d3e8e379b96dcc800c9dd69e925122b3d73d8f59a563c3afa patch_hash = 979e22b24b16c927040700dfd8319cd6ba29bf52a14dbc66b1cb4ea60504f14a
wrapdb_version = 1.3.7-3 wrapdb_version = 1.3.7-4
[provide] [provide]
vorbis = vorbis_dep vorbis = vorbis_dep
vorbisfile = vorbisfile_dep vorbisfile = vorbisfile_dep
vorbisenc = vorbisenc_dep vorbisenc = vorbisenc_dep
...@@ -288,7 +288,8 @@ if enable_database ...@@ -288,7 +288,8 @@ if enable_database
dependencies: [ dependencies: [
log_dep, log_dep,
tag_dep, tag_dep,
storage_glue_dep, fs_dep,
storage_plugins_dep,
gtest_dep, gtest_dep,
], ],
), ),
...@@ -320,11 +321,6 @@ if curl_dep.found() ...@@ -320,11 +321,6 @@ if curl_dep.found()
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
curl_dep, curl_dep,
# Explicitly linking with zlib here works around a linker
# failure on Windows, because our Windows CURL build is
# statically linked and thus declares no dependency on zlib
zlib_dep,
], ],
) )
......
...@@ -19,6 +19,7 @@ test( ...@@ -19,6 +19,7 @@ test(
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
net_dep, net_dep,
util_dep,
gtest_dep, gtest_dep,
], ],
), ),
......
...@@ -148,8 +148,6 @@ public: ...@@ -148,8 +148,6 @@ public:
} }
DecoderCommand GetCommand() noexcept override { DecoderCommand GetCommand() noexcept override {
assert(IsInitialized());
if (seek_where != SongTime{}) { if (seek_where != SongTime{}) {
if (!seekable) if (!seekable)
return DecoderCommand::STOP; return DecoderCommand::STOP;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "ls.hxx" #include "ls.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "db/DatabaseSong.hxx" #include "db/DatabaseSong.hxx"
#include "storage/Registry.hxx"
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#include "storage/plugins/LocalStorage.hxx" #include "storage/plugins/LocalStorage.hxx"
#include "Mapper.hxx" #include "Mapper.hxx"
...@@ -36,6 +37,13 @@ uri_supported_scheme(const char *uri) noexcept ...@@ -36,6 +37,13 @@ uri_supported_scheme(const char *uri) noexcept
return strncmp(uri, "http://", 7) == 0; return strncmp(uri, "http://", 7) == 0;
} }
const StoragePlugin *
GetStoragePluginByUri(const char *) noexcept
{
// dummy symbol
return nullptr;
}
static constexpr auto music_directory = PATH_LITERAL("/music"); static constexpr auto music_directory = PATH_LITERAL("/music");
static Storage *storage; static Storage *storage;
......
#!/usr/bin/env python3 #!/usr/bin/env -S python3 -u
import os, os.path import os, os.path
import sys, subprocess import sys, subprocess
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment