Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9866adff95 | ||
![]() |
a8b0c55818 | ||
![]() |
cac88e8be5 | ||
![]() |
e9f6a3482c | ||
![]() |
5d2e80f188 | ||
![]() |
cfd4d5b13e | ||
![]() |
06514aec63 | ||
![]() |
4ded1ae67b | ||
![]() |
1da974e3fa | ||
![]() |
94f06f0946 | ||
![]() |
d9eec8a455 | ||
![]() |
eaecbcafb2 | ||
![]() |
73b5d0a9b9 | ||
![]() |
c2d0f35e7a | ||
![]() |
ab99a57997 | ||
![]() |
c8ebaf3521 | ||
![]() |
52d00f7e30 | ||
![]() |
309491a6d8 | ||
![]() |
e7bfd32ccc | ||
![]() |
6f283b52ab | ||
![]() |
32bddfabea | ||
![]() |
1944c826bc | ||
![]() |
619bb60b26 | ||
![]() |
c549e16ed1 | ||
![]() |
01c9c4507f | ||
![]() |
8c9d7bf07e | ||
![]() |
44ef34db88 | ||
![]() |
5781f223f6 | ||
![]() |
e4c8ebe056 | ||
![]() |
76b25a1377 | ||
![]() |
ccc3ee663b | ||
![]() |
0626661764 | ||
![]() |
31db04a3ca | ||
![]() |
0c7163b9db | ||
![]() |
7d78cad8af | ||
![]() |
912530ed20 | ||
![]() |
d3f37199b9 | ||
![]() |
a4748d84b0 | ||
![]() |
8f847ec381 | ||
![]() |
3a70f09dd3 | ||
![]() |
568f63100b | ||
![]() |
3e25916b37 | ||
![]() |
5f9438dae6 | ||
![]() |
99e65c58ce | ||
![]() |
df71b07e9d | ||
![]() |
2694195215 | ||
![]() |
66450d1f3c | ||
![]() |
76efea3aa7 | ||
![]() |
7ab0dfc8ce | ||
![]() |
15ff7c4cad | ||
![]() |
9ab9b97f20 | ||
![]() |
88d92aceab | ||
![]() |
a2ce4352c8 | ||
![]() |
84f43ccde8 | ||
![]() |
38704c9cf3 | ||
![]() |
910d0ec92b | ||
![]() |
3b05c89765 | ||
![]() |
e77b3fa46f | ||
![]() |
12147f6d58 | ||
![]() |
40bc60d6ae | ||
![]() |
7778210269 | ||
![]() |
6229210d51 | ||
![]() |
5d0d5b5d97 | ||
![]() |
1aa3c1e543 | ||
![]() |
b90e32fe4e | ||
![]() |
1f4df2a64d | ||
![]() |
2efc1db6a9 | ||
![]() |
e2d4654e20 | ||
![]() |
2b8f1170a6 | ||
![]() |
5c4743441e | ||
![]() |
cb288439a4 | ||
![]() |
69f741e8a6 | ||
![]() |
4b4f47002b | ||
![]() |
615c301961 | ||
![]() |
dc07180e48 | ||
![]() |
d3b235bab5 | ||
![]() |
7c920ddebe | ||
![]() |
bbc088ae4e | ||
![]() |
fe195257d8 | ||
![]() |
57d5df8118 | ||
![]() |
59792cb0b8 | ||
![]() |
cc557c4d60 | ||
![]() |
956c5faebb | ||
![]() |
cd0396c1f1 | ||
![]() |
79f9b268bb | ||
![]() |
b45f3c8deb | ||
![]() |
f8a8de87e4 | ||
![]() |
2183f0553c |
NEWS
android
doc
meson.buildpython/build
src
LogInit.cxxMain.cxxPartition.cxxPartition.hxxPlaylistFile.cxxStateFile.cxxStateFileConfig.cxx
android
archive
command
db
decoder
encoder
plugins
filter
fs
input
java
lib
mixer
output
storage
system
time
subprojects
test
win32
39
NEWS
39
NEWS
@@ -1,3 +1,42 @@
|
||||
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)
|
||||
* storage
|
||||
- curl: fix crash if web server does not understand WebDAV
|
||||
|
@@ -2,10 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="66"
|
||||
android:versionName="0.23.7">
|
||||
android:versionCode="70"
|
||||
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"
|
||||
android:required="false" />
|
||||
|
@@ -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') != ''
|
||||
debug_apk = custom_target(
|
||||
'mpd-debug.apk',
|
||||
output: 'mpd-debug.apk',
|
||||
input: unsigned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
jarsigner,
|
||||
'-keystore', get_option('android_debug_keystore'),
|
||||
'-storepass', 'android',
|
||||
'-signedjar', '@OUTPUT@',
|
||||
'@INPUT@',
|
||||
'androiddebugkey',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--out', '@OUTPUT@',
|
||||
'--debuggable-apk-permitted',
|
||||
'-ks', get_option('android_debug_keystore'),
|
||||
'--ks-key-alias', 'androiddebugkey',
|
||||
'--ks-pass', 'pass:android',
|
||||
],
|
||||
build_by_default: true
|
||||
)
|
||||
@@ -31,29 +43,16 @@ endif
|
||||
|
||||
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
|
||||
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',
|
||||
output: 'mpd.apk',
|
||||
input: unaligned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
android_zipalign,
|
||||
'-f', '4',
|
||||
'@INPUT@', '@OUTPUT@',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--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
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import os, os.path
|
||||
import sys, subprocess
|
||||
|
@@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
|
||||
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
|
||||
|
||||
javac = find_program('javac')
|
||||
jarsigner = find_program('jarsigner')
|
||||
apksigner = find_program('apksigner')
|
||||
rsvg_convert = find_program('rsvg-convert')
|
||||
convert = find_program('convert')
|
||||
zip = find_program('zip')
|
||||
|
@@ -38,7 +38,10 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# 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.
|
||||
#release = version + '~git'
|
||||
|
||||
@@ -47,7 +50,7 @@ version = '0.23.8'
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# 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
|
||||
# non-false value, then it is used:
|
||||
|
@@ -11,6 +11,12 @@ Music Player Daemon
|
||||
client
|
||||
protocol
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: man pages:
|
||||
|
||||
mpd.1
|
||||
mpd.conf.5
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
@@ -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.
|
||||
* - **speed N**
|
||||
- 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
|
||||
----
|
||||
@@ -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
|
||||
them to a blank value), general curl configuration from environment
|
||||
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
|
||||
will be in effect.
|
||||
variables such as ``http_proxy`` will be in effect.
|
||||
|
||||
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
|
@@ -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.
|
||||
|
||||
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
|
||||
<sles_output>` output plugin can be used for local playback.
|
||||
@@ -197,7 +199,7 @@ Compiling for Android
|
||||
You need:
|
||||
|
||||
* 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
|
||||
<https://ninja-build.org/>`__
|
||||
* cmake
|
||||
|
13
meson.build
13
meson.build
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.23.8',
|
||||
version: '0.23.11',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@@ -251,6 +251,14 @@ endif
|
||||
|
||||
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',
|
||||
'src/Log.cxx',
|
||||
@@ -352,7 +360,7 @@ sources = [
|
||||
'src/TagStream.cxx',
|
||||
'src/TagAny.cxx',
|
||||
'src/TimePrint.cxx',
|
||||
'src/mixer/Volume.cxx',
|
||||
'src/mixer/Memento.cxx',
|
||||
'src/PlaylistFile.cxx',
|
||||
]
|
||||
|
||||
@@ -382,6 +390,7 @@ endif
|
||||
|
||||
if enable_database
|
||||
sources += [
|
||||
'src/storage/StorageState.cxx',
|
||||
'src/queue/PlaylistUpdate.cxx',
|
||||
'src/command/StorageCommands.cxx',
|
||||
'src/command/DatabaseCommands.cxx',
|
||||
|
@@ -43,20 +43,22 @@ opus = AutotoolsProject(
|
||||
)
|
||||
|
||||
flac = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
|
||||
'8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.4.2.tar.xz',
|
||||
'e322d58a1f48d23d9dd38f432672865f6f79e73a6f9cc5a5f57fcaa83eb5a8e4',
|
||||
'lib/libFLAC.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-stack-smash-protection',
|
||||
'--disable-xmms-plugin', '--disable-cpplibs',
|
||||
'--disable-doxygen-docs',
|
||||
'--disable-programs',
|
||||
],
|
||||
subdirs=['include', 'src/libFLAC'],
|
||||
)
|
||||
|
||||
zlib = ZlibProject(
|
||||
'http://zlib.net/zlib-1.2.12.tar.xz',
|
||||
'7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18',
|
||||
'http://zlib.net/zlib-1.2.13.tar.xz',
|
||||
'd14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98',
|
||||
'lib/libz.a',
|
||||
)
|
||||
|
||||
@@ -112,16 +114,20 @@ libmodplug = AutotoolsProject(
|
||||
)
|
||||
|
||||
libopenmpt = AutotoolsProject(
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
|
||||
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
|
||||
'6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
|
||||
'lib/libopenmpt.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-openmpt123',
|
||||
'--disable-examples',
|
||||
'--disable-tests',
|
||||
'--disable-doxygen-doc',
|
||||
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
|
||||
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
|
||||
'--without-flac',
|
||||
],
|
||||
base='libopenmpt-0.5.12+release.autotools',
|
||||
base='libopenmpt-0.6.6+release.autotools',
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
@@ -151,8 +157,8 @@ gme = CmakeProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz',
|
||||
'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b',
|
||||
'http://ffmpeg.org/releases/ffmpeg-5.1.2.tar.xz',
|
||||
'619e706d662c8420859832ddc259cd4d4096a48a2ce1eefd052db9e440eef3dc',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -166,7 +172,6 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-swscale',
|
||||
'--disable-postproc',
|
||||
'--disable-avfilter',
|
||||
'--disable-lzo',
|
||||
'--disable-faan',
|
||||
'--disable-pixelutils',
|
||||
'--disable-network',
|
||||
@@ -382,19 +387,18 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
openssl = OpenSSLProject(
|
||||
'https://www.openssl.org/source/openssl-3.0.5.tar.gz',
|
||||
'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a',
|
||||
'https://www.openssl.org/source/openssl-3.0.7.tar.gz',
|
||||
'83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e',
|
||||
'include/openssl/ossl_typ.h',
|
||||
)
|
||||
|
||||
curl = CmakeProject(
|
||||
'https://curl.se/download/curl-7.84.0.tar.xz',
|
||||
'2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8',
|
||||
'https://curl.se/download/curl-7.86.0.tar.xz',
|
||||
'2d61116e5f485581f6d59865377df4463f2e788677ac43222b496d4e49fb627b',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'-DBUILD_CURL_EXE=OFF',
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
|
||||
'-DCURL_DISABLE_LDAP=ON',
|
||||
'-DCURL_DISABLE_TELNET=ON',
|
||||
'-DCURL_DISABLE_DICT=ON',
|
||||
@@ -423,8 +427,8 @@ curl = CmakeProject(
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
|
||||
'7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
|
||||
'637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -435,7 +439,7 @@ libnfs = AutotoolsProject(
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-5.0.1',
|
||||
base='libnfs-libnfs-5.0.2',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
@@ -446,7 +450,7 @@ jack = JackProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2',
|
||||
'475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39',
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2',
|
||||
'1e19565d82e43bc59209a168f5ac899d3ba471d55c7610c677d4ccf2c9c500c0',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@@ -82,8 +82,8 @@ endian = '{endian}'
|
||||
def configure(toolchain, src, build, args=()):
|
||||
cross_file = make_cross_file(toolchain)
|
||||
configure = [
|
||||
'meson',
|
||||
src, build,
|
||||
'meson', 'setup',
|
||||
build, src,
|
||||
|
||||
'--prefix', toolchain.install_prefix,
|
||||
|
||||
|
@@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
|
||||
getenv("NOTIFY_SOCKET") != nullptr) {
|
||||
/* if MPD was started as a systemd
|
||||
service, default to journal (which
|
||||
is connected to fd=2) */
|
||||
is connected to stdout&stderr) */
|
||||
out_fd = STDOUT_FILENO;
|
||||
return;
|
||||
}
|
||||
#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");
|
||||
#endif
|
||||
#ifdef HAVE_SYSLOG
|
||||
|
48
src/Main.cxx
48
src/Main.cxx
@@ -590,19 +590,46 @@ MainConfigured(const CommandLineOptions &options,
|
||||
|
||||
#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
|
||||
AndroidMain()
|
||||
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
|
||||
AndroidMain(JNIEnv *env)
|
||||
{
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
||||
if (!sdcard.IsNull()) {
|
||||
const auto config_path =
|
||||
sdcard / Path::FromFS("mpd.conf");
|
||||
if (FileExists(config_path))
|
||||
ReadConfigFile(raw_config, config_path);
|
||||
}
|
||||
LoadConfigFile(env, raw_config);
|
||||
|
||||
MainConfigured(options, raw_config);
|
||||
}
|
||||
@@ -614,9 +641,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
||||
Java::Init(env);
|
||||
Java::Object::Initialise(env);
|
||||
Java::File::Initialise(env);
|
||||
|
||||
Environment::Initialise(env);
|
||||
AtScopeExit(env) { Environment::Deinitialise(env); };
|
||||
|
||||
Context::Initialise(env);
|
||||
|
||||
context = new Context(env, _context);
|
||||
AtScopeExit() { delete context; };
|
||||
|
||||
@@ -625,7 +655,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
||||
AtScopeExit() { delete logListener; };
|
||||
|
||||
try {
|
||||
AndroidMain();
|
||||
AndroidMain(env);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@
|
||||
#include "Log.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
#include "client/Listener.hxx"
|
||||
#include "client/Client.hxx"
|
||||
@@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
|
||||
void
|
||||
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
|
||||
{
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
|
||||
/* notify clients */
|
||||
EmitIdle(IDLE_MIXER);
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "queue/Listener.hxx"
|
||||
#include "output/MultipleOutputs.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "mixer/Memento.hxx"
|
||||
#include "player/Control.hxx"
|
||||
#include "player/Listener.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
@@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
||||
|
||||
MultipleOutputs outputs;
|
||||
|
||||
MixerMemento mixer_memento;
|
||||
|
||||
PlayerControl pc;
|
||||
|
||||
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
|
||||
|
@@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
|
||||
*/
|
||||
|
||||
return std::strchr(name_utf8, '/') == nullptr &&
|
||||
#ifdef _WIN32
|
||||
std::strchr(name_utf8, '\\') == nullptr &&
|
||||
#endif
|
||||
std::strchr(name_utf8, '\n') == nullptr &&
|
||||
std::strchr(name_utf8, '\r') == nullptr;
|
||||
}
|
||||
|
@@ -27,7 +27,6 @@
|
||||
#include "storage/StorageState.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
|
||||
void
|
||||
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_playlist_version = playlist_state_get_hash(partition.playlist,
|
||||
partition.pc);
|
||||
@@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
|
||||
bool
|
||||
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_playlist_version != playlist_state_get_hash(partition.playlist,
|
||||
partition.pc)
|
||||
@@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
|
||||
inline void
|
||||
StateFile::Write(BufferedOutputStream &os)
|
||||
{
|
||||
save_sw_volume_state(os);
|
||||
partition.mixer_memento.SaveSoftwareVolumeState(os);
|
||||
audio_output_state_save(os, partition.outputs);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
@@ -125,7 +124,7 @@ try {
|
||||
|
||||
const char *line;
|
||||
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) ||
|
||||
playlist_state_restore(config, line, file, song_loader,
|
||||
partition.playlist,
|
||||
|
@@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
if (path.IsNull()) {
|
||||
const auto cache_dir = GetUserCacheDir();
|
||||
const auto cache_dir = GetAppCacheDir();
|
||||
if (cache_dir.IsNull())
|
||||
return;
|
||||
|
||||
|
@@ -26,19 +26,30 @@
|
||||
|
||||
#include "AudioManager.hxx"
|
||||
|
||||
AllocatedPath
|
||||
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
|
||||
static jmethodID getExternalFilesDir_method,
|
||||
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())};
|
||||
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||
"(Ljava/lang/String;)Ljava/io/File;");
|
||||
assert(method);
|
||||
getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||
"(Ljava/lang/String;)Ljava/io/File;");
|
||||
getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
|
||||
"()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)
|
||||
return nullptr;
|
||||
|
||||
@@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
jmethodID method = env->GetMethodID(cls, "getCacheDir",
|
||||
"()Ljava/io/File;");
|
||||
assert(method);
|
||||
|
||||
jobject file = env->CallObjectMethod(Get(), method);
|
||||
jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
|
||||
if (Java::DiscardException(env) || file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
@@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
|
||||
{
|
||||
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");
|
||||
jobject am = env->CallObjectMethod(Get(), method, name.Get());
|
||||
jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
|
||||
if (Java::DiscardException(env) || am == nullptr)
|
||||
return nullptr;
|
||||
|
||||
|
@@ -27,12 +27,21 @@ class AudioManager;
|
||||
|
||||
class Context : public Java::GlobalObject {
|
||||
public:
|
||||
/**
|
||||
* Global initialisation. Looks up the methods of the
|
||||
* Context Java class.
|
||||
*/
|
||||
static void Initialise(JNIEnv *env) noexcept;
|
||||
|
||||
Context(JNIEnv *env, jobject obj) noexcept
|
||||
:Java::GlobalObject(env, obj) {}
|
||||
|
||||
/**
|
||||
* @param type the subdirectory name; may be nullptr
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetExternalFilesDir(JNIEnv *env,
|
||||
const char *type) noexcept;
|
||||
const char *type=nullptr) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
||||
|
@@ -25,13 +25,13 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
||||
namespace Environment {
|
||||
static Java::TrivialClass cls;
|
||||
static jmethodID getExternalStorageDirectory_method;
|
||||
static jmethodID getExternalStoragePublicDirectory_method;
|
||||
}
|
||||
|
||||
static Java::TrivialClass cls;
|
||||
static jmethodID getExternalStorageDirectory_method;
|
||||
static jmethodID getExternalStoragePublicDirectory_method;
|
||||
|
||||
void
|
||||
Environment::Initialise(JNIEnv *env) noexcept
|
||||
Initialise(JNIEnv *env) noexcept
|
||||
{
|
||||
cls.Find(env, "android/os/Environment");
|
||||
|
||||
@@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Deinitialise(JNIEnv *env) noexcept
|
||||
Deinitialise(JNIEnv *env) noexcept
|
||||
{
|
||||
cls.Clear(env);
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
Environment::getExternalStorageDirectory() noexcept
|
||||
getExternalStorageDirectory(JNIEnv *env) noexcept
|
||||
{
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
|
||||
jobject file =
|
||||
env->CallStaticObjectMethod(cls,
|
||||
getExternalStorageDirectory_method);
|
||||
@@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
Environment::getExternalStoragePublicDirectory(const char *type) noexcept
|
||||
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
|
||||
{
|
||||
if (getExternalStoragePublicDirectory_method == nullptr)
|
||||
/* needs API level 8 */
|
||||
return nullptr;
|
||||
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
|
||||
Java::String type2(env, type);
|
||||
jobject file = env->CallStaticObjectMethod(Environment::cls,
|
||||
Environment::getExternalStoragePublicDirectory_method,
|
||||
jobject file = env->CallStaticObjectMethod(cls,
|
||||
getExternalStoragePublicDirectory_method,
|
||||
type2.Get());
|
||||
if (file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return Java::File::ToAbsolutePath(env, file);
|
||||
}
|
||||
|
||||
} // namespace Environment
|
||||
|
@@ -17,27 +17,29 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_ANDROID_ENVIRONMENT_HXX
|
||||
#define MPD_ANDROID_ENVIRONMENT_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
class AllocatedPath;
|
||||
|
||||
namespace Environment {
|
||||
void Initialise(JNIEnv *env) noexcept;
|
||||
void Deinitialise(JNIEnv *env) noexcept;
|
||||
|
||||
/**
|
||||
* Determine the mount point of the external SD card.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStorageDirectory() noexcept;
|
||||
void
|
||||
Initialise(JNIEnv *env) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
|
||||
}
|
||||
void
|
||||
Deinitialise(JNIEnv *env) noexcept;
|
||||
|
||||
#endif
|
||||
/**
|
||||
* Determine the mount point of the external SD card.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath
|
||||
getExternalStorageDirectory(JNIEnv *env) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath
|
||||
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
|
||||
|
||||
} // namespace Environment
|
||||
|
@@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream {
|
||||
assert(fill <= data.size());
|
||||
assert(position <= fill);
|
||||
|
||||
return {&data[position], &data[fill]};
|
||||
return {data.data() + position, data.data() + fill};
|
||||
}
|
||||
|
||||
void Consume(size_t nbytes) noexcept {
|
||||
|
@@ -22,6 +22,10 @@ if libzzip_dep.found()
|
||||
found_archive_plugin = true
|
||||
endif
|
||||
|
||||
if not found_archive_plugin
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
archive_plugins = static_library(
|
||||
'archive_plugins',
|
||||
archive_plugins_sources,
|
||||
|
@@ -33,7 +33,6 @@
|
||||
#include "TimePrint.hxx"
|
||||
#include "decoder/DecoderPrint.hxx"
|
||||
#include "ls.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
@@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
|
||||
{
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
const auto volume = volume_level_get(partition.outputs);
|
||||
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
|
||||
if (volume >= 0)
|
||||
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
||||
|
||||
@@ -337,7 +336,9 @@ handle_setvol(Client &client, Request args, Response &)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -346,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
|
||||
{
|
||||
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) {
|
||||
r.Error(ACK_ERROR_SYSTEM, "No mixer");
|
||||
return CommandResult::ERROR;
|
||||
@@ -360,8 +363,10 @@ handle_volume(Client &client, Request args, Response &r)
|
||||
else if (new_volume > 100)
|
||||
new_volume = 100;
|
||||
|
||||
if (new_volume != old_volume)
|
||||
volume_level_change(outputs, new_volume);
|
||||
if (new_volume != old_volume) {
|
||||
mixer_memento.SetVolume(outputs, new_volume);
|
||||
partition.EmitIdle(IDLE_MIXER);
|
||||
}
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
@@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
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");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
@@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
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");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
@@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
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");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
@@ -25,7 +25,6 @@
|
||||
#include "SingleMode.hxx"
|
||||
#include "client/Client.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
@@ -131,7 +130,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
||||
|
||||
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)
|
||||
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
||||
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "config/Param.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
@@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
|
||||
} else {
|
||||
/* if there is no override, use the cache directory */
|
||||
|
||||
const AllocatedPath cache_dir = GetUserCacheDir();
|
||||
const AllocatedPath cache_dir = GetAppCacheDir();
|
||||
if (cache_dir.IsNull())
|
||||
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();
|
||||
if (db_file_utf8.empty())
|
||||
return nullptr;
|
||||
|
||||
ConfigBlock block;
|
||||
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,
|
||||
listener, block);
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@
|
||||
#include "lib/ffmpeg/Format.hxx"
|
||||
#include "lib/ffmpeg/Codec.hxx"
|
||||
#include "lib/ffmpeg/SampleFormat.hxx"
|
||||
#include "lib/ffmpeg/LibFmt.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "FfmpegMetaData.hxx"
|
||||
#include "FfmpegIo.hxx"
|
||||
@@ -523,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
||||
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,
|
||||
sample_format,
|
||||
codec_context->channels);
|
||||
channels);
|
||||
|
||||
const SignedSongTime total_time =
|
||||
av_stream.duration != (int64_t)AV_NOPTS_VALUE
|
||||
@@ -635,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
||||
AV_TIME_BASE_Q));
|
||||
|
||||
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 {
|
||||
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
|
||||
ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
|
||||
codec_params.channels));
|
||||
channels));
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
|
@@ -21,10 +21,13 @@
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "FfmpegIo.hxx"
|
||||
#include "libavutil/mem.h"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/mem.h>
|
||||
}
|
||||
|
||||
AvioStream::~AvioStream()
|
||||
{
|
||||
if (io != nullptr) {
|
||||
|
@@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
|
||||
|
||||
FLAC__StreamEncoder *const fse;
|
||||
const unsigned compression;
|
||||
const bool oggflac;
|
||||
|
||||
PcmBuffer expand_buffer;
|
||||
|
||||
@@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
|
||||
}
|
||||
|
||||
static void
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
|
||||
const AudioFormat &audio_format)
|
||||
{
|
||||
unsigned bits_per_sample;
|
||||
@@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||
throw FormatRuntimeError("error setting flac sample rate to %d",
|
||||
audio_format.sample_rate);
|
||||
|
||||
if (!FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||
if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||
GenerateSerial()))
|
||||
throw FormatRuntimeError("error setting ogg serial number");
|
||||
}
|
||||
@@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
|
||||
:Encoder(_oggchaining),
|
||||
audio_format(_audio_format), fse(_fse),
|
||||
compression(_compression),
|
||||
oggflac(_oggflac),
|
||||
output_buffer(8192)
|
||||
{
|
||||
/* this immediately outputs data through callback */
|
||||
|
||||
auto init_status = _oggflac ?
|
||||
auto init_status = oggflac ?
|
||||
FLAC__stream_encoder_init_ogg_stream(fse,
|
||||
nullptr, WriteCallback,
|
||||
nullptr, nullptr, nullptr,
|
||||
@@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
|
||||
throw std::runtime_error("FLAC__stream_encoder_new() failed");
|
||||
|
||||
try {
|
||||
flac_encoder_setup(fse, compression, audio_format);
|
||||
flac_encoder_setup(fse, compression, oggflac, audio_format);
|
||||
} catch (...) {
|
||||
FLAC__stream_encoder_delete(fse);
|
||||
throw;
|
||||
@@ -222,7 +224,7 @@ void
|
||||
FlacEncoder::SendTag(const Tag &tag)
|
||||
{
|
||||
/* 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_VorbisComment_Entry entry;
|
||||
|
@@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
|
||||
buffer_sink(_buffer_sink),
|
||||
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
|
||||
in_sample_rate(in_audio_format.sample_rate),
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
|
||||
in_channels(in_audio_format.channels),
|
||||
#endif
|
||||
in_audio_frame_size(in_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>
|
||||
@@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
|
||||
frame.Unref();
|
||||
frame->format = in_format;
|
||||
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;
|
||||
#endif
|
||||
frame->nb_samples = src.size / in_audio_frame_size;
|
||||
|
||||
frame.GetBuffer();
|
||||
|
@@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
|
||||
|
||||
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 out_audio_frame_size;
|
||||
|
@@ -23,6 +23,8 @@
|
||||
#include "ReplayGainInfo.hxx"
|
||||
#include "ReplayGainConfig.hxx"
|
||||
#include "mixer/MixerControl.hxx"
|
||||
#include "mixer/MixerInternal.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
@@ -171,9 +173,11 @@ ReplayGainFilter::Update()
|
||||
try {
|
||||
mixer_set_volume(mixer, _volume);
|
||||
|
||||
/* TODO: emit this idle event only for the
|
||||
current partition */
|
||||
idle_add(IDLE_MIXER);
|
||||
/* invoke the mixer's listener manually, just
|
||||
in case the mixer implementation didn't do
|
||||
that already (this depends on the
|
||||
implementation) */
|
||||
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception(),
|
||||
"Failed to update hardware mixer");
|
||||
|
@@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
|
||||
|
||||
#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
|
||||
* error.
|
||||
|
@@ -53,6 +53,12 @@
|
||||
#include "Main.hxx"
|
||||
#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)
|
||||
class PasswdEntry
|
||||
{
|
||||
@@ -254,7 +260,8 @@ GetUserMusicDir() noexcept
|
||||
#elif defined(USE_XDG)
|
||||
return GetUserDir("XDG_MUSIC_DIR");
|
||||
#elif defined(ANDROID)
|
||||
return Environment::getExternalStoragePublicDirectory("Music");
|
||||
return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
|
||||
"Music");
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@@ -283,6 +290,24 @@ GetUserCacheDir() noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
@@ -296,7 +321,7 @@ GetUserRuntimeDir() noexcept
|
||||
AllocatedPath
|
||||
GetAppRuntimeDir() noexcept
|
||||
{
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && !defined(ANDROID)
|
||||
/* systemd specific; see systemd.exec(5) */
|
||||
if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
|
||||
if (auto dir = StringView{runtime_directory}.Split(':').first;
|
||||
@@ -306,8 +331,8 @@ GetAppRuntimeDir() noexcept
|
||||
|
||||
#ifdef USE_XDG
|
||||
if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
|
||||
auto dir = user_dir / Path::FromFS("mpd");
|
||||
mkdir(dir.c_str(), 0700);
|
||||
auto dir = user_dir / app_filename;
|
||||
CreateDirectoryNoThrow(dir);
|
||||
return dir;
|
||||
}
|
||||
#endif
|
||||
|
@@ -43,6 +43,13 @@ GetUserMusicDir() noexcept;
|
||||
AllocatedPath
|
||||
GetUserCacheDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains cache directory for this application.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetAppCacheDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the runtime directory for the current user.
|
||||
*/
|
||||
|
@@ -45,6 +45,14 @@
|
||||
|
||||
#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 {
|
||||
cdrom_drive_t *const drv;
|
||||
CdIo_t *const cdio;
|
||||
@@ -65,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream {
|
||||
lsn_from(_lsn_from),
|
||||
buffer_lsn(-1)
|
||||
{
|
||||
/* Set reading mode for full paranoia, but allow
|
||||
skipping sectors. */
|
||||
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
|
||||
para.SetMode(mode_flags);
|
||||
|
||||
/* seek to beginning of the track */
|
||||
para.Seek(lsn_from);
|
||||
@@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
|
||||
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
|
||||
input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||
{
|
||||
@@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||
value);
|
||||
}
|
||||
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 {
|
||||
|
@@ -439,6 +439,14 @@ CurlInputStream::InitEasy()
|
||||
request->SetVerifyPeer(verify_peer);
|
||||
request->SetVerifyHost(verify_host);
|
||||
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
|
||||
|
@@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept
|
||||
AllocatedPath
|
||||
Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
assert(_file != nullptr);
|
||||
|
||||
LocalObject file(env, _file);
|
||||
|
||||
const jstring path = GetAbsolutePath(env, file);
|
||||
|
@@ -89,6 +89,16 @@ public:
|
||||
String(JNIEnv *_env, const char *_value) noexcept
|
||||
: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 {
|
||||
return {env, s, env->GetStringUTFChars(s, nullptr)};
|
||||
}
|
||||
|
@@ -123,6 +123,14 @@ public:
|
||||
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) {
|
||||
easy.SetNoBody(value);
|
||||
}
|
||||
|
@@ -48,7 +48,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
|
||||
Frame frame;
|
||||
frame->format = ToFfmpegSampleFormat(in_audio_format.format);
|
||||
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;
|
||||
#endif
|
||||
frame->nb_samples = 1;
|
||||
|
||||
frame.GetBuffer();
|
||||
@@ -75,8 +79,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
|
||||
if (sample_format == SampleFormat::UNDEFINED)
|
||||
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,
|
||||
frame->channels);
|
||||
out_channels);
|
||||
}
|
||||
|
||||
} // namespace Ffmpeg
|
||||
|
@@ -38,7 +38,11 @@ InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer)
|
||||
assert(frame.nb_samples > 0);
|
||||
|
||||
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;
|
||||
#endif
|
||||
const std::size_t n_frames = frame.nb_samples;
|
||||
|
||||
int plane_size;
|
||||
|
39
src/lib/ffmpeg/LibFmt.hxx
Normal file
39
src/lib/ffmpeg/LibFmt.hxx
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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()
|
||||
'Util.cxx',
|
||||
'Init.cxx',
|
||||
]
|
||||
elif not get_option('iconv').disabled()
|
||||
# an installed iconv library will make the builtin iconv() unavailable,
|
||||
# so search for the library first and pass it as (possible) dependency
|
||||
iconv_dep = compiler.find_library('libiconv', required: false)
|
||||
have_iconv = compiler.has_function('iconv',
|
||||
dependencies: iconv_dep,
|
||||
prefix : '#include <iconv.h>')
|
||||
if not have_iconv and get_option('iconv').enabled()
|
||||
error('iconv() not available')
|
||||
else
|
||||
if meson.version().version_compare('>= 0.60')
|
||||
iconv_dep = dependency('iconv', required: get_option('iconv'))
|
||||
conf.set('HAVE_ICONV', iconv_dep.found())
|
||||
elif not get_option('iconv').disabled()
|
||||
iconv_open_snippet = '''#include <iconv.h>
|
||||
int main() {
|
||||
iconv_open("","");
|
||||
}'''
|
||||
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
|
||||
conf.set('HAVE_ICONV', have_iconv)
|
||||
endif
|
||||
|
||||
icu = static_library(
|
||||
|
@@ -17,14 +17,11 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "Volume.hxx"
|
||||
#include "Memento.hxx"
|
||||
#include "output/MultipleOutputs.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "system/PeriodClock.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -32,24 +29,8 @@
|
||||
|
||||
#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
|
||||
volume_level_get(const MultipleOutputs &outputs) noexcept
|
||||
MixerMemento::GetVolume(const MultipleOutputs &outputs) noexcept
|
||||
{
|
||||
if (last_hardware_volume >= 0 &&
|
||||
!hardware_volume_clock.CheckUpdate(std::chrono::seconds(1)))
|
||||
@@ -60,8 +41,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept
|
||||
return last_hardware_volume;
|
||||
}
|
||||
|
||||
static bool
|
||||
software_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
inline bool
|
||||
MixerMemento::SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
assert(volume <= 100);
|
||||
|
||||
@@ -71,8 +52,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
inline void
|
||||
MixerMemento::SetHardwareVolume(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
/* reset the cache */
|
||||
last_hardware_volume = -1;
|
||||
@@ -81,19 +62,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
}
|
||||
|
||||
void
|
||||
volume_level_change(MultipleOutputs &outputs, unsigned volume)
|
||||
MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
assert(volume <= 100);
|
||||
|
||||
volume_software_set = volume;
|
||||
|
||||
idle_add(IDLE_MIXER);
|
||||
|
||||
hardware_volume_change(outputs, volume);
|
||||
SetHardwareVolume(outputs, volume);
|
||||
}
|
||||
|
||||
bool
|
||||
read_sw_volume_state(const char *line, MultipleOutputs &outputs)
|
||||
MixerMemento::LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs)
|
||||
{
|
||||
char *end = nullptr;
|
||||
long int sv;
|
||||
@@ -104,21 +83,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs)
|
||||
|
||||
sv = strtol(line, &end, 10);
|
||||
if (*end == 0 && sv >= 0 && sv <= 100)
|
||||
software_volume_change(outputs, sv);
|
||||
else
|
||||
FmtWarning(volume_domain,
|
||||
"Can't parse software volume: {}", line);
|
||||
SetSoftwareVolume(outputs, sv);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
save_sw_volume_state(BufferedOutputStream &os)
|
||||
MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const
|
||||
{
|
||||
os.Format(SW_VOLUME_STATE "%u\n", volume_software_set);
|
||||
}
|
||||
|
||||
unsigned
|
||||
sw_volume_state_get_hash() noexcept
|
||||
{
|
||||
return volume_software_set;
|
||||
}
|
75
src/mixer/Memento.hxx
Normal file
75
src/mixer/Memento.hxx
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2003-2021 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "system/PeriodClock.hxx"
|
||||
|
||||
class MultipleOutputs;
|
||||
class BufferedOutputStream;
|
||||
|
||||
/**
|
||||
* Cache for hardware/software volume levels.
|
||||
*/
|
||||
class MixerMemento {
|
||||
unsigned volume_software_set = 100;
|
||||
|
||||
/** the cached hardware mixer value; invalid if negative */
|
||||
int last_hardware_volume = -1;
|
||||
|
||||
/** the age of #last_hardware_volume */
|
||||
PeriodClock hardware_volume_clock;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Flush the hardware volume cache.
|
||||
*/
|
||||
void InvalidateHardwareVolume() noexcept {
|
||||
last_hardware_volume = -1;
|
||||
}
|
||||
|
||||
[[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;
|
||||
}
|
||||
|
||||
private:
|
||||
bool SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume);
|
||||
void SetHardwareVolume(MultipleOutputs &outputs, unsigned volume);
|
||||
};
|
@@ -188,8 +188,8 @@ MultipleOutputs::SetSoftwareVolume(unsigned volume) noexcept
|
||||
auto *mixer = ao->GetMixer();
|
||||
|
||||
if (mixer != nullptr &&
|
||||
(&mixer->plugin == &software_mixer_plugin ||
|
||||
&mixer->plugin == &null_mixer_plugin))
|
||||
(mixer->IsPlugin(software_mixer_plugin) ||
|
||||
mixer->IsPlugin(null_mixer_plugin)))
|
||||
mixer_set_volume(mixer, volume);
|
||||
}
|
||||
}
|
||||
|
@@ -92,7 +92,7 @@ mixer_close(Mixer *mixer)
|
||||
void
|
||||
mixer_auto_close(Mixer *mixer)
|
||||
{
|
||||
if (!mixer->plugin.global)
|
||||
if (!mixer->IsGlobal())
|
||||
mixer_close(mixer);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ mixer_get_volume(Mixer *mixer)
|
||||
|
||||
assert(mixer != nullptr);
|
||||
|
||||
if (mixer->plugin.global && !mixer->failure)
|
||||
if (mixer->IsGlobal() && !mixer->failure)
|
||||
mixer_open(mixer);
|
||||
|
||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||
@@ -128,7 +128,7 @@ mixer_set_volume(Mixer *mixer, unsigned volume)
|
||||
assert(mixer != nullptr);
|
||||
assert(volume <= 100);
|
||||
|
||||
if (mixer->plugin.global && !mixer->failure)
|
||||
if (mixer->IsGlobal() && !mixer->failure)
|
||||
mixer_open(mixer);
|
||||
|
||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||
|
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2021 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_VOLUME_HXX
|
||||
#define MPD_VOLUME_HXX
|
||||
|
||||
class MultipleOutputs;
|
||||
class BufferedOutputStream;
|
||||
|
||||
void
|
||||
InvalidateHardwareVolume() noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
int
|
||||
volume_level_get(const MultipleOutputs &outputs) noexcept;
|
||||
|
||||
/**
|
||||
* Throws on error.
|
||||
*/
|
||||
void
|
||||
volume_level_change(MultipleOutputs &outputs, unsigned volume);
|
||||
|
||||
bool
|
||||
read_sw_volume_state(const char *line, MultipleOutputs &outputs);
|
||||
|
||||
void
|
||||
save_sw_volume_state(BufferedOutputStream &os);
|
||||
|
||||
/**
|
||||
* 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
|
||||
sw_volume_state_get_hash() noexcept;
|
||||
|
||||
#endif
|
@@ -28,13 +28,15 @@
|
||||
#include "MultipleOutputs.hxx"
|
||||
#include "Client.hxx"
|
||||
#include "mixer/MixerControl.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "mixer/Memento.hxx"
|
||||
#include "Idle.hxx"
|
||||
|
||||
extern unsigned audio_output_state_version;
|
||||
|
||||
bool
|
||||
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
audio_output_enable_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx)
|
||||
{
|
||||
if (idx >= outputs.Size())
|
||||
return false;
|
||||
@@ -46,7 +48,7 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
idle_add(IDLE_OUTPUT);
|
||||
|
||||
if (ao.GetMixer() != nullptr) {
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
|
||||
@@ -58,7 +60,9 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
}
|
||||
|
||||
bool
|
||||
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
audio_output_disable_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx)
|
||||
{
|
||||
if (idx >= outputs.Size())
|
||||
return false;
|
||||
@@ -72,7 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
auto *mixer = ao.GetMixer();
|
||||
if (mixer != nullptr) {
|
||||
mixer_close(mixer);
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
|
||||
@@ -84,7 +88,9 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
}
|
||||
|
||||
bool
|
||||
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
||||
audio_output_toggle_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx)
|
||||
{
|
||||
if (idx >= outputs.Size())
|
||||
return false;
|
||||
@@ -97,7 +103,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
||||
auto *mixer = ao.GetMixer();
|
||||
if (mixer != nullptr) {
|
||||
mixer_close(mixer);
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
}
|
||||
|
@@ -28,26 +28,33 @@
|
||||
#define MPD_OUTPUT_COMMAND_HXX
|
||||
|
||||
class MultipleOutputs;
|
||||
class MixerMemento;
|
||||
|
||||
/**
|
||||
* Enables an audio output. Returns false if the specified output
|
||||
* does not exist.
|
||||
*/
|
||||
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
|
||||
* does not exist.
|
||||
*/
|
||||
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
|
||||
* does not exist.
|
||||
*/
|
||||
bool
|
||||
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
|
||||
audio_output_toggle_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx);
|
||||
|
||||
#endif
|
||||
|
@@ -812,8 +812,12 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
||||
fmt::format("Failed to open ALSA device \"{}\"",
|
||||
GetDevice()).c_str());
|
||||
|
||||
const char *pcm_name = snd_pcm_name(pcm);
|
||||
if (pcm_name == nullptr)
|
||||
pcm_name = "?";
|
||||
|
||||
FmtDebug(alsa_output_domain, "opened {} type={}",
|
||||
snd_pcm_name(pcm),
|
||||
pcm_name,
|
||||
snd_pcm_type_name(snd_pcm_type(pcm)));
|
||||
|
||||
#ifdef ENABLE_DSD
|
||||
|
@@ -47,6 +47,15 @@
|
||||
|
||||
#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 StringBuffer<64>
|
||||
@@ -160,13 +169,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block)
|
||||
static constexpr AudioObjectPropertyAddress default_system_output_device{
|
||||
kAudioHardwarePropertyDefaultSystemOutputDevice,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress default_output_device{
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
const auto &aopa =
|
||||
@@ -195,9 +204,9 @@ int
|
||||
OSXOutput::GetVolume()
|
||||
{
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
|
||||
@@ -211,9 +220,9 @@ OSXOutput::SetVolume(unsigned new_volume)
|
||||
{
|
||||
Float32 vol = new_volume / 100.0;
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
UInt32 size = sizeof(vol);
|
||||
OSStatus status = AudioObjectSetPropertyData(dev_id,
|
||||
@@ -366,25 +375,25 @@ osx_output_set_device_format(AudioDeviceID dev_id,
|
||||
static constexpr AudioObjectPropertyAddress aopa_device_streams = {
|
||||
kAudioDevicePropertyStreams,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
|
||||
kAudioStreamPropertyDirection,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
|
||||
kAudioStreamPropertyPhysicalFormat,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
OSStatus err;
|
||||
@@ -484,7 +493,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioDevicePropertyHogMode,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
pid_t hog_pid;
|
||||
@@ -538,7 +547,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
|
||||
static constexpr AudioObjectPropertyAddress aopa_name{
|
||||
kAudioObjectPropertyName,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
char actual_name[256];
|
||||
@@ -561,7 +570,7 @@ FindAudioDeviceByName(const char *name)
|
||||
static constexpr AudioObjectPropertyAddress aopa_hw_devices{
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
const auto ids =
|
||||
|
@@ -514,6 +514,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||
PW_KEY_MEDIA_ROLE, "Music",
|
||||
PW_KEY_APP_NAME, "Music Player Daemon",
|
||||
PW_KEY_APP_ICON_NAME, "mpd",
|
||||
nullptr);
|
||||
|
||||
pw_properties_setf(props, PW_KEY_NODE_NAME, "mpd.%s", name);
|
||||
|
@@ -17,7 +17,6 @@ storage_glue = static_library(
|
||||
'CompositeStorage.cxx',
|
||||
'MemoryDirectoryReader.cxx',
|
||||
'Configured.cxx',
|
||||
'StorageState.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
boost_dep,
|
||||
@@ -31,4 +30,3 @@ storage_glue_dep = declare_dependency(
|
||||
storage_plugins_dep,
|
||||
],
|
||||
)
|
||||
|
||||
|
@@ -34,7 +34,6 @@
|
||||
#include "event/InjectEvent.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "thread/Cond.hxx"
|
||||
#include "time/Parser.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
@@ -171,8 +170,9 @@ struct DavResponse {
|
||||
}
|
||||
};
|
||||
|
||||
[[gnu::pure]]
|
||||
static unsigned
|
||||
ParseStatus(const char *s)
|
||||
ParseStatus(const char *s) noexcept
|
||||
{
|
||||
/* skip the "HTTP/1.1" prefix */
|
||||
const char *space = std::strchr(s, ' ');
|
||||
@@ -182,37 +182,37 @@ ParseStatus(const char *s)
|
||||
return strtoul(space + 1, nullptr, 10);
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
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());
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static std::chrono::system_clock::time_point
|
||||
ParseTimeStamp(const char *s)
|
||||
ParseTimeStamp(const char *s) noexcept
|
||||
{
|
||||
try {
|
||||
// TODO: make this more robust
|
||||
return ParseTimePoint(s, "%a, %d %b %Y %T");
|
||||
} catch (...) {
|
||||
return std::chrono::system_clock::time_point::min();
|
||||
}
|
||||
return std::chrono::system_clock::from_time_t(curl_getdate(s, nullptr));
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
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());
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static uint64_t
|
||||
ParseU64(const char *s)
|
||||
ParseU64(const char *s) noexcept
|
||||
{
|
||||
return strtoull(s, nullptr, 10);
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
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());
|
||||
}
|
||||
@@ -278,6 +278,7 @@ public:
|
||||
"<a:resourcetype/>"
|
||||
"<a:getcontenttype/>"
|
||||
"<a:getcontentlength/>"
|
||||
"<a:getlastmodified/>"
|
||||
"</a:prop>"
|
||||
"</a:propfind>");
|
||||
}
|
||||
|
@@ -70,8 +70,11 @@ FormatLastError(DWORD code, const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
char buffer[512];
|
||||
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)...);
|
||||
if (length >= max_prefix)
|
||||
length = max_prefix - 1;
|
||||
char *p = buffer + length;
|
||||
*p++ = ':';
|
||||
*p++ = ' ';
|
||||
|
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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 */
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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
|
@@ -1,6 +1,5 @@
|
||||
time = static_library(
|
||||
'time',
|
||||
'Parser.cxx',
|
||||
'Convert.cxx',
|
||||
'ISO8601.cxx',
|
||||
'Math.cxx',
|
||||
|
@@ -1,12 +1,12 @@
|
||||
[wrap-file]
|
||||
directory = expat-2.4.8
|
||||
source_url = https://github.com/libexpat/libexpat/releases/download/R_2_4_8/expat-2.4.8.tar.xz
|
||||
source_filename = expat-2.4.8.tar.bz2
|
||||
source_hash = f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25
|
||||
patch_filename = expat_2.4.8-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.4.8-1/get_patch
|
||||
patch_hash = 9aec253a2c6d1c0feb852c5c6920298d14701eeec7acc6832bb402438b52112a
|
||||
directory = expat-2.5.0
|
||||
source_url = https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.xz
|
||||
source_filename = expat-2.5.0.tar.bz2
|
||||
source_hash = ef2420f0232c087801abf705e89ae65f6257df6b7931d37846a193ef2e8cdcbe
|
||||
patch_filename = expat_2.5.0-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.5.0-1/get_patch
|
||||
patch_hash = 0d0d6e07ed21cf4892126a8270f5fd182012ab34b3ebe24932a2bef5ca608a61
|
||||
wrapdb_version = 2.5.0-1
|
||||
|
||||
[provide]
|
||||
expat = expat_dep
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
[wrap-file]
|
||||
directory = fmt-8.1.1
|
||||
source_url = https://github.com/fmtlib/fmt/archive/8.1.1.tar.gz
|
||||
source_filename = fmt-8.1.1.tar.gz
|
||||
source_hash = 3d794d3cf67633b34b2771eb9f073bde87e846e0d395d254df7b211ef1ec7346
|
||||
patch_filename = fmt_8.1.1-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_8.1.1-2/get_patch
|
||||
patch_hash = cd001046281330a8862591780a9ea71a1fa594edd0d015deb24e44680c9ea33b
|
||||
wrapdb_version = 8.1.1-2
|
||||
directory = fmt-9.1.0
|
||||
source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz
|
||||
source_filename = fmt-9.1.0.tar.gz
|
||||
source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2
|
||||
patch_filename = fmt_9.1.0-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-1/get_patch
|
||||
patch_hash = 4557b9ba87b3eb63694ed9b21d1a2117d4a97ca56b91085b10288e9a5294adf8
|
||||
wrapdb_version = 9.1.0-1
|
||||
|
||||
[provide]
|
||||
fmt = fmt_dep
|
||||
|
@@ -1,12 +1,12 @@
|
||||
[wrap-file]
|
||||
directory = sqlite-amalgamation-3380000
|
||||
source_url = https://sqlite.org/2022/sqlite-amalgamation-3380000.zip
|
||||
source_filename = sqlite-amalgamation-3380000.zip
|
||||
source_hash = e055f6054e97747a135c89e36520c0a423249e8a91c5fc445163f4a6adb20df6
|
||||
patch_filename = sqlite3_3.38.0-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.38.0-1/get_patch
|
||||
patch_hash = 49e30bf010ff63ab772d5417885e6905379025ceac80382e292c6dbd3a9da744
|
||||
directory = sqlite-amalgamation-3390300
|
||||
source_url = https://sqlite.org/2022/sqlite-amalgamation-3390300.zip
|
||||
source_filename = sqlite-amalgamation-3390300.zip
|
||||
source_hash = a89db3030d229d860ae56a8bac50ac9761434047ae886e47e7c8f9f428fa98ad
|
||||
patch_filename = sqlite3_3.39.3-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.39.3-1/get_patch
|
||||
patch_hash = f5c41ff7b3da1108ed221b9a820b41188550cafb8a6c3d247bb40bd598775050
|
||||
wrapdb_version = 3.39.3-1
|
||||
|
||||
[provide]
|
||||
sqlite3 = sqlite3_dep
|
||||
|
||||
|
@@ -3,13 +3,12 @@ directory = libvorbis-1.3.7
|
||||
source_url = https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz
|
||||
source_filename = libvorbis-1.3.7.tar.xz
|
||||
source_hash = b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b
|
||||
patch_filename = vorbis_1.3.7-3_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-3/get_patch
|
||||
patch_hash = 6cb90a61ede8c64d3e8e379b96dcc800c9dd69e925122b3d73d8f59a563c3afa
|
||||
wrapdb_version = 1.3.7-3
|
||||
patch_filename = vorbis_1.3.7-4_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-4/get_patch
|
||||
patch_hash = 979e22b24b16c927040700dfd8319cd6ba29bf52a14dbc66b1cb4ea60504f14a
|
||||
wrapdb_version = 1.3.7-4
|
||||
|
||||
[provide]
|
||||
vorbis = vorbis_dep
|
||||
vorbisfile = vorbisfile_dep
|
||||
vorbisenc = vorbisenc_dep
|
||||
|
||||
|
@@ -288,7 +288,8 @@ if enable_database
|
||||
dependencies: [
|
||||
log_dep,
|
||||
tag_dep,
|
||||
storage_glue_dep,
|
||||
fs_dep,
|
||||
storage_plugins_dep,
|
||||
gtest_dep,
|
||||
],
|
||||
),
|
||||
@@ -320,11 +321,6 @@ if curl_dep.found()
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
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(
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
net_dep,
|
||||
util_dep,
|
||||
gtest_dep,
|
||||
],
|
||||
),
|
||||
|
@@ -148,8 +148,6 @@ public:
|
||||
}
|
||||
|
||||
DecoderCommand GetCommand() noexcept override {
|
||||
assert(IsInitialized());
|
||||
|
||||
if (seek_where != SongTime{}) {
|
||||
if (!seekable)
|
||||
return DecoderCommand::STOP;
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include "ls.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "db/DatabaseSong.hxx"
|
||||
#include "storage/Registry.hxx"
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#include "storage/plugins/LocalStorage.hxx"
|
||||
#include "Mapper.hxx"
|
||||
@@ -36,6 +37,13 @@ uri_supported_scheme(const char *uri) noexcept
|
||||
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 Storage *storage;
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import os, os.path
|
||||
import sys, subprocess
|
||||
|
Reference in New Issue
Block a user