Compare commits
215 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c67372f8af | ||
|
|
00789de7d4 | ||
|
|
5ece9685c2 | ||
|
|
e7c5a42821 | ||
|
|
36e6079c57 | ||
|
|
e5f23678ca | ||
|
|
749ad7cd83 | ||
|
|
0b59f4eaee | ||
|
|
402663de74 | ||
|
|
eaa66c7ee3 | ||
|
|
996714d6ff | ||
|
|
fe48e5596f | ||
|
|
d7744d2b8e | ||
|
|
33ee35ab92 | ||
|
|
5b291ff768 | ||
|
|
39d6816a6d | ||
|
|
6517b2d2ac | ||
|
|
bfdf13dca3 | ||
|
|
daefc61aa4 | ||
|
|
6fed6e50e4 | ||
|
|
bc9e074822 | ||
|
|
8047102542 | ||
|
|
fe5b81e180 | ||
|
|
f032925c2d | ||
|
|
8125a5dddb | ||
|
|
154170e475 | ||
|
|
fb83936feb | ||
|
|
db8bf52f7d | ||
|
|
756f0b8027 | ||
|
|
b1fba8d3d7 | ||
|
|
e606044271 | ||
|
|
bcbb3371ff | ||
|
|
de632882d1 | ||
|
|
745e492d15 | ||
|
|
c5dc615efe | ||
|
|
beeb02025e | ||
|
|
cdf7062597 | ||
|
|
346084da1e | ||
|
|
bbceb5eb91 | ||
|
|
90d85319c2 | ||
|
|
3d03683e7d | ||
|
|
d8a74802d1 | ||
|
|
191919d1b1 | ||
|
|
df38e7565b | ||
|
|
cb49a03fd7 | ||
|
|
faee5bbb78 | ||
|
|
7befab7e83 | ||
|
|
4244e61214 | ||
|
|
46eab05045 | ||
|
|
5ca137c73c | ||
|
|
760238fe16 | ||
|
|
a99b4abae8 | ||
|
|
472881cb95 | ||
|
|
c4efc37ad8 | ||
|
|
691b6a236e | ||
|
|
5c7243d3ad | ||
|
|
44cfdff39a | ||
|
|
5eedda691a | ||
|
|
a30d5e1b6a | ||
|
|
8ef09a0a71 | ||
|
|
e8044663b3 | ||
|
|
8444c33514 | ||
|
|
2b7328b434 | ||
|
|
ca705e1e37 | ||
|
|
d9f9b3df10 | ||
|
|
a43ee97746 | ||
|
|
43c32372e7 | ||
|
|
5716cde1fb | ||
|
|
b7a99b4a4b | ||
|
|
24741c5d06 | ||
|
|
6b3a282db4 | ||
|
|
7583cfe9b7 | ||
|
|
aafc9ce75b | ||
|
|
fea326530b | ||
|
|
8925cc17d8 | ||
|
|
14412c867f | ||
|
|
c5cc256bf2 | ||
|
|
563c7318f9 | ||
|
|
e92129f449 | ||
|
|
374cc51f77 | ||
|
|
1008d5f67c | ||
|
|
8e07ea7ad8 | ||
|
|
d751df0a73 | ||
|
|
2c084781b0 | ||
|
|
ae7d550a01 | ||
|
|
30d97fe8a0 | ||
|
|
5cb0080052 | ||
|
|
8e4ca23727 | ||
|
|
bdc861f058 | ||
|
|
8925040262 | ||
|
|
c065950ced | ||
|
|
257a77fa35 | ||
|
|
4e5d6e560b | ||
|
|
d276d8eda2 | ||
|
|
ebcb5e9368 | ||
|
|
69f09648a4 | ||
|
|
9adda30c38 | ||
|
|
d2d4a0251e | ||
|
|
f7b6431b6f | ||
|
|
03b9bd3a9e | ||
|
|
61aed60f6d | ||
|
|
2cc323c9fe | ||
|
|
f24ab120ee | ||
|
|
68349bc55c | ||
|
|
209364adf2 | ||
|
|
24afdee35c | ||
|
|
7aea285361 | ||
|
|
47a7707df1 | ||
|
|
6fdae1139f | ||
|
|
6c240f667c | ||
|
|
3040ddb5ec | ||
|
|
fdb28eb0c4 | ||
|
|
7ded244a61 | ||
|
|
8ed533acf3 | ||
|
|
a27580d0cc | ||
|
|
905db05cf9 | ||
|
|
4242aee21e | ||
|
|
e71bd2a08b | ||
|
|
e53a4d0a9e | ||
|
|
159389164a | ||
|
|
0a92fbc18e | ||
|
|
138c29320b | ||
|
|
8f00dbea45 | ||
|
|
f3fd2eb618 | ||
|
|
fc92db83cf | ||
|
|
3b0f8d5516 | ||
|
|
a5273d6992 | ||
|
|
b18074f899 | ||
|
|
3d8067a041 | ||
|
|
f6fe001fa9 | ||
|
|
32a5bf043b | ||
|
|
8d2079482f | ||
|
|
c331c75fde | ||
|
|
6080c3b4ba | ||
|
|
5ccfcffcc1 | ||
|
|
afe2aaa5f6 | ||
|
|
9b11caa0e6 | ||
|
|
a689b881d3 | ||
|
|
e94c436264 | ||
|
|
bad829509e | ||
|
|
9c66b0414a | ||
|
|
4d453a8313 | ||
|
|
61d7b436a2 | ||
|
|
cdddaf21b0 | ||
|
|
b267ba5f0a | ||
|
|
8270043053 | ||
|
|
c00ce42bca | ||
|
|
3852ddbbce | ||
|
|
672bc3ab67 | ||
|
|
62229f14da | ||
|
|
a4c925c8d7 | ||
|
|
60610e90b1 | ||
|
|
90184e0ce7 | ||
|
|
9c3e1d450a | ||
|
|
60f2116202 | ||
|
|
4ff2532330 | ||
|
|
9c15760c4d | ||
|
|
e1c43ec65f | ||
|
|
4dd10894ba | ||
|
|
608d7ec1e7 | ||
|
|
8474599ed6 | ||
|
|
ab39f64fc0 | ||
|
|
185fbca282 | ||
|
|
6e3b2fd844 | ||
|
|
dab39dc778 | ||
|
|
8cd5e79fbd | ||
|
|
1de3ac6c78 | ||
|
|
abe06a5fa6 | ||
|
|
85c27840a3 | ||
|
|
81c16273c5 | ||
|
|
801ae86b5d | ||
|
|
5619fd0bba | ||
|
|
200258c7c3 | ||
|
|
5418bb49fb | ||
|
|
3449c14ff5 | ||
|
|
36a89e8fe7 | ||
|
|
8e6a21a9c2 | ||
|
|
c560ec8ea6 | ||
|
|
56c234b410 | ||
|
|
82743dfd02 | ||
|
|
33694642bd | ||
|
|
c71242d743 | ||
|
|
c45f113856 | ||
|
|
e0a8fd398c | ||
|
|
3e97058151 | ||
|
|
51b1dd8672 | ||
|
|
98a7d8da6c | ||
|
|
acb29f792f | ||
|
|
cd364023ae | ||
|
|
8d34a1cfc6 | ||
|
|
73a1f078a6 | ||
|
|
b7ce452308 | ||
|
|
5faf76051d | ||
|
|
5fe70a3417 | ||
|
|
7a68b1e71f | ||
|
|
d5468dfe89 | ||
|
|
976372ff63 | ||
|
|
9abb686eeb | ||
|
|
f24bcc7f42 | ||
|
|
89800324cb | ||
|
|
050e30418c | ||
|
|
5397d18ed9 | ||
|
|
42eb69f46f | ||
|
|
f1ad21d2bf | ||
|
|
535a099a27 | ||
|
|
50003f6ad2 | ||
|
|
0914644d2b | ||
|
|
5ad6e7fec5 | ||
|
|
0bb943ba3e | ||
|
|
80a0cf694f | ||
|
|
0c9e25b3c4 | ||
|
|
943a67c805 | ||
|
|
881d91f86b | ||
|
|
54d57fdcc2 | ||
|
|
f6f30d6d64 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@
|
||||
/output/
|
||||
|
||||
__pycache__/
|
||||
|
||||
/.clangd/
|
||||
/compile_commands.json
|
||||
|
||||
42
.travis.yml
42
.travis.yml
@@ -1,6 +1,6 @@
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
jobs:
|
||||
include:
|
||||
# Ubuntu Bionic (18.04) with GCC 7
|
||||
- os: linux
|
||||
@@ -17,9 +17,9 @@ matrix:
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
@@ -39,9 +39,9 @@ matrix:
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
@@ -61,9 +61,9 @@ matrix:
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
@@ -86,9 +86,9 @@ matrix:
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||
@@ -112,9 +112,9 @@ matrix:
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||
@@ -126,6 +126,24 @@ matrix:
|
||||
packages:
|
||||
- ccache
|
||||
- meson
|
||||
- icu4c
|
||||
- ffmpeg
|
||||
- libnfs
|
||||
- yajl
|
||||
- libupnp
|
||||
- libid3tag
|
||||
- chromaprint
|
||||
- libsamplerate
|
||||
- libsoxr
|
||||
# libzzip appears to be broken on Homebrew: "ld: library not found for -lzzip"
|
||||
#- libzzip
|
||||
- flac
|
||||
- opus
|
||||
- libvorbis
|
||||
- faad2
|
||||
- wavpack
|
||||
- libmpdclient
|
||||
update: true
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
||||
|
||||
@@ -148,7 +166,7 @@ install:
|
||||
# belonging to nss. You can unlink it" during gtest install
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
|
||||
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
||||
|
||||
before_script:
|
||||
- ccache -s
|
||||
|
||||
98
NEWS
98
NEWS
@@ -1,3 +1,101 @@
|
||||
ver 0.21.25 (2020/07/06)
|
||||
* protocol:
|
||||
- fix crash when using "rangeid" while playing
|
||||
* database
|
||||
- simple: automatically scan new mounts
|
||||
- upnp: fix compatibility with Plex DLNA
|
||||
* storage
|
||||
- fix disappearing mounts after mounting twice
|
||||
- udisks: fix reading ".mpdignore"
|
||||
* input
|
||||
- file: detect premature end of file
|
||||
- smbclient: don't send credentials to MPD clients
|
||||
* decoder
|
||||
- opus: apply pre-skip and end trimming
|
||||
- opus: fix memory leak
|
||||
- opus: fix crash bug
|
||||
- vorbis: fix crash bug
|
||||
* output
|
||||
- osx: improve sample rate selection
|
||||
- osx: fix noise while stopping
|
||||
* neighbor
|
||||
- upnp: fix crash during shutdown
|
||||
* Windows/Android:
|
||||
- fix Boost detection after breaking change in Meson 0.54
|
||||
|
||||
ver 0.21.24 (2020/06/10)
|
||||
* protocol
|
||||
- "tagtypes" requires no permissions
|
||||
* database
|
||||
- simple: fix crash when mounting twice
|
||||
* decoder
|
||||
- modplug: fix Windows build failure
|
||||
- wildmidi: attempt to detect WildMidi using pkg-config
|
||||
- wildmidi: fix Windows build failure
|
||||
* player
|
||||
- don't restart current song if seeking beyond end
|
||||
* Android
|
||||
- enable the decoder plugins GME, ModPlug and WildMidi
|
||||
- fix build failure with Android NDK r21
|
||||
* Windows
|
||||
- fix stream playback
|
||||
- enable the decoder plugins GME, ModPlug and WildMidi
|
||||
- work around Meson bug breaking the Windows build with GCC 10
|
||||
* fix unit test failure
|
||||
|
||||
ver 0.21.23 (2020/04/23)
|
||||
* protocol
|
||||
- add tag fallback for AlbumSort
|
||||
* storage
|
||||
- curl: fix corrupt "href" values in the presence of XML entities
|
||||
- curl: unescape "href" values
|
||||
* input
|
||||
- nfs: fix crash bug
|
||||
- nfs: fix freeze bug on reconnect
|
||||
* decoder
|
||||
- gme: adapt to API change in the upcoming version 0.7.0
|
||||
* output
|
||||
- alsa: implement channel mapping for 5.0 and 7.0
|
||||
* player
|
||||
- drain outputs at end of song in "single" mode
|
||||
* Windows
|
||||
- fix case insensitive search
|
||||
|
||||
ver 0.21.22 (2020/04/02)
|
||||
* database
|
||||
- simple: optimize startup
|
||||
* input
|
||||
- curl: fix streaming errors on Android
|
||||
* playlist
|
||||
- rss: support MIME type application/xml
|
||||
* mixer
|
||||
- android: new mixer plugin for "sles" output
|
||||
* Android
|
||||
- TV support
|
||||
* Windows
|
||||
- fix time zone offset check
|
||||
* fix build failures with uClibc-ng
|
||||
|
||||
ver 0.21.21 (2020/03/19)
|
||||
* configuration
|
||||
- fix bug in "metadata_to_use" setting
|
||||
* playlist
|
||||
- asx, xspf: fix corrupt tags in the presence of XML entities
|
||||
* archive
|
||||
- iso9660: skip empty file names to work around libcdio bug
|
||||
* decoder
|
||||
- gme: ignore empty tags
|
||||
* output
|
||||
- solaris: port to NetBSD
|
||||
* raise default "max_connections" value to 100
|
||||
|
||||
ver 0.21.20 (2020/02/16)
|
||||
* decoder
|
||||
- audiofile, ffmpeg, sndfile: handle MIME type "audio/wav"
|
||||
- ffmpeg: fix playback of AIFF and TTA
|
||||
- vorbis, opus: fix seeking in small files
|
||||
* fix backwards seeking on ARM (and other non-x86 CPUs)
|
||||
|
||||
ver 0.21.19 (2020/01/17)
|
||||
* configuration
|
||||
- allow overriding top-level settings in includes
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="42"
|
||||
android:versionName="0.21.19">
|
||||
android:versionCode="48"
|
||||
android:versionName="0.21.25">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||
|
||||
<uses-feature android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:banner="@drawable/icon"
|
||||
android:label="@string/app_name">
|
||||
<activity android:name=".Settings"
|
||||
android:label="@string/app_name">
|
||||
@@ -22,6 +29,14 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".Settings"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".Receiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
|
||||
@@ -26,7 +26,7 @@ android_abis = {
|
||||
'ndk_arch': 'arm',
|
||||
'toolchain_arch': 'arm-linux-androideabi',
|
||||
'llvm_triple': 'armv7-linux-androideabi',
|
||||
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
||||
'cflags': '-fpic -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp',
|
||||
},
|
||||
|
||||
'arm64-v8a': {
|
||||
@@ -34,7 +34,7 @@ android_abis = {
|
||||
'ndk_arch': 'arm64',
|
||||
'toolchain_arch': 'aarch64-linux-android',
|
||||
'llvm_triple': 'aarch64-linux-android',
|
||||
'cflags': '',
|
||||
'cflags': '-fpic',
|
||||
},
|
||||
|
||||
'x86': {
|
||||
@@ -42,7 +42,7 @@ android_abis = {
|
||||
'ndk_arch': 'x86',
|
||||
'toolchain_arch': 'x86',
|
||||
'llvm_triple': 'i686-linux-android',
|
||||
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
},
|
||||
|
||||
'x86_64': {
|
||||
@@ -50,7 +50,7 @@ android_abis = {
|
||||
'ndk_arch': 'x86_64',
|
||||
'toolchain_arch': 'x86_64',
|
||||
'llvm_triple': 'x86_64-linux-android',
|
||||
'cflags': '-m64',
|
||||
'cflags': '-fPIC -m64',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ class AndroidNdkToolchain:
|
||||
llvm_triple = abi_info['llvm_triple'] + android_api_level
|
||||
|
||||
common_flags = '-Os -g'
|
||||
common_flags += ' -fPIC'
|
||||
common_flags += ' ' + abi_info['cflags']
|
||||
|
||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||
@@ -169,6 +168,9 @@ thirdparty_libs = [
|
||||
opus,
|
||||
flac,
|
||||
libid3tag,
|
||||
libmodplug,
|
||||
wildmidi,
|
||||
gme,
|
||||
ffmpeg,
|
||||
curl,
|
||||
libexpat,
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.musicpd;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
@@ -35,6 +36,9 @@ import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Main extends Service implements Runnable {
|
||||
private static final String TAG = "Main";
|
||||
private static final String REMOTE_ERROR = "MPD process was killed";
|
||||
@@ -156,11 +160,36 @@ public class Main extends Service implements Runnable {
|
||||
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||
}
|
||||
|
||||
private Notification.Builder createNotificationBuilderWithChannel() {
|
||||
final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (notificationManager == null)
|
||||
return null;
|
||||
|
||||
final String id = "org.musicpd";
|
||||
final String name = "MPD service";
|
||||
final int importance = 3; /* NotificationManager.IMPORTANCE_DEFAULT */
|
||||
|
||||
try {
|
||||
Class<?> ncClass = Class.forName("android.app.NotificationChannel");
|
||||
Constructor<?> ncCtor = ncClass.getConstructor(String.class, CharSequence.class, int.class);
|
||||
Object nc = ncCtor.newInstance(id, name, importance);
|
||||
|
||||
Method nmCreateNotificationChannelMethod =
|
||||
NotificationManager.class.getMethod("createNotificationChannel", ncClass);
|
||||
nmCreateNotificationChannelMethod.invoke(notificationManager, nc);
|
||||
|
||||
Constructor nbCtor = Notification.Builder.class.getConstructor(Context.class, String.class);
|
||||
return (Notification.Builder) nbCtor.newInstance(this, id);
|
||||
} catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "error creating the NotificationChannel", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void start() {
|
||||
if (mThread != null)
|
||||
return;
|
||||
mThread = new Thread(this);
|
||||
mThread.start();
|
||||
|
||||
final Intent mainIntent = new Intent(this, Settings.class);
|
||||
mainIntent.setAction("android.intent.action.MAIN");
|
||||
@@ -168,13 +197,25 @@ public class Main extends Service implements Runnable {
|
||||
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
Notification notification = new Notification.Builder(this)
|
||||
.setContentTitle(getText(R.string.notification_title_mpd_running))
|
||||
Notification.Builder nBuilder;
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Build.VERSION_CODES.O */)
|
||||
{
|
||||
nBuilder = createNotificationBuilderWithChannel();
|
||||
if (nBuilder == null)
|
||||
return;
|
||||
}
|
||||
else
|
||||
nBuilder = new Notification.Builder(this);
|
||||
|
||||
Notification notification = nBuilder.setContentTitle(getText(R.string.notification_title_mpd_running))
|
||||
.setContentText(getText(R.string.notification_text_mpd_running))
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setContentIntent(contentIntent)
|
||||
.build();
|
||||
|
||||
mThread = new Thread(this);
|
||||
mThread.start();
|
||||
|
||||
startForeground(R.string.notification_title_mpd_running, notification);
|
||||
startService(new Intent(this, Main.class));
|
||||
}
|
||||
|
||||
@@ -105,12 +105,13 @@ public class Settings extends Activity {
|
||||
else
|
||||
mRunButton.setChecked(false);
|
||||
mFirstRun = true;
|
||||
mTextStatus.setText("");
|
||||
break;
|
||||
case MSG_STARTED:
|
||||
Log.d(TAG, "onStarted");
|
||||
mRunButton.setChecked(true);
|
||||
mFirstRun = true;
|
||||
mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX
|
||||
mTextStatus.setText("MPD service started");
|
||||
break;
|
||||
case MSG_LOG:
|
||||
if (mLogListArray.size() > MAX_LOGS)
|
||||
|
||||
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.19'
|
||||
version = '0.21.25'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
||||
@@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
|
||||
curl
|
||||
----
|
||||
|
||||
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
|
||||
A WebDAV client using libcurl. It is used when :code:`music_directory`
|
||||
contains a ``http://`` or ``https://`` URI, for example
|
||||
:samp:`https://the.server/dav/`.
|
||||
|
||||
smbclient
|
||||
---------
|
||||
|
||||
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
|
||||
Load music files from a SMB/CIFS server. It is used when
|
||||
:code:`music_directory` contains a ``smb://`` URI, for example
|
||||
:samp:`smb://myfileserver/Music`.
|
||||
|
||||
nfs
|
||||
---
|
||||
|
||||
Load music files from a NFS server. It is used when :code:`music_directory` contains a nfs:// URI according to RFC2224, for example :samp:`nfs://servername/path`.
|
||||
Load music files from a NFS server. It is used when
|
||||
:code:`music_directory` contains a ``nfs://`` URI according to
|
||||
RFC2224, for example :samp:`nfs://servername/path`.
|
||||
|
||||
This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD` is not allowed to bind to "privileged ports", the NFS server needs to enable the "insecure" setting; example :file:`/etc/exports`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/srv/mp3 192.168.1.55(ro,insecure)
|
||||
|
||||
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
|
||||
See :ref:`input_nfs` for more information.
|
||||
|
||||
udisks
|
||||
------
|
||||
@@ -162,7 +162,10 @@ curl
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
@@ -182,7 +185,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
|
||||
ffmpeg
|
||||
------
|
||||
|
||||
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
|
||||
Access to various network protocols implemented by the FFmpeg library:
|
||||
``gopher://``, ``rtp://``, ``rtsp://``, ``rtmp://``, ``rtmpt://``,
|
||||
``rtmps://``
|
||||
|
||||
file
|
||||
----
|
||||
@@ -194,30 +199,51 @@ mms
|
||||
|
||||
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
|
||||
|
||||
.. _input_nfs:
|
||||
|
||||
nfs
|
||||
---
|
||||
|
||||
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
|
||||
Allows :program:`MPD` to access files on NFS servers without actually
|
||||
mounting them (i.e. with :program:`libnfs` in userspace, without help
|
||||
from the kernel's VFS layer). All URIs with the ``nfs://`` scheme are
|
||||
used according to RFC2224. Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
mpc add nfs://servername/path/filename.ogg
|
||||
|
||||
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
|
||||
This plugin uses :program:`libnfs`, which supports only NFS version 3.
|
||||
Since :program:`MPD` is not allowed to bind to so-called "privileged
|
||||
ports", the NFS server needs to enable the ``insecure`` setting;
|
||||
example :file:`/etc/exports`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/srv/mp3 192.168.1.55(ro,insecure)
|
||||
|
||||
Don't fear: this will not make your file server insecure; the flag was
|
||||
named a time long ago when privileged ports were thought to be
|
||||
meaningful for security. By today's standards, NFSv3 is not secure at
|
||||
all, and if you believe it is, you're already doomed.
|
||||
|
||||
smbclient
|
||||
---------
|
||||
|
||||
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
|
||||
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba
|
||||
or Microsoft Windows). All URIs with the ``smb://`` scheme are
|
||||
used. Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
mpc add smb://servername/sharename/filename.ogg
|
||||
mpc add smb://username:password@servername/sharename/filename.ogg
|
||||
|
||||
qobuz
|
||||
-----
|
||||
|
||||
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
|
||||
Play songs from the commercial streaming service Qobuz. It plays URLs
|
||||
in the form ``qobuz://track/ID``, e.g.:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
@@ -243,7 +269,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
|
||||
tidal
|
||||
-----
|
||||
|
||||
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
|
||||
Play songs from the commercial streaming service `Tidal
|
||||
<http://tidal.com/>`_. It plays URLs in the form ``tidal://track/ID``,
|
||||
e.g.:
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
@@ -464,7 +464,8 @@ Querying :program:`MPD`'s status
|
||||
- ``songs``: number of songs
|
||||
- ``uptime``: daemon uptime in seconds
|
||||
- ``db_playtime``: sum of all song times in the database in seconds
|
||||
- ``db_update``: last db update in UNIX time
|
||||
- ``db_update``: last db update in UNIX time (seconds since
|
||||
1970-01-01 UTC)
|
||||
- ``playtime``: time length of music played
|
||||
|
||||
Playback options
|
||||
|
||||
@@ -695,7 +695,7 @@ These settings are various limitations to prevent :program:`MPD` from using too
|
||||
* - **connection_timeout SECONDS**
|
||||
- If a client does not send any new data in this time period, the connection is closed. Clients waiting in "idle" mode are excluded from this. Default is 60.
|
||||
* - **max_connections NUMBER**
|
||||
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 5.
|
||||
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 100.
|
||||
* - **max_playlist_length NUMBER**
|
||||
- The maximum number of songs that can be in the playlist. Default is 16384.
|
||||
* - **max_command_list_size KBYTES**
|
||||
|
||||
21
meson.build
21
meson.build
@@ -1,11 +1,12 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.19',
|
||||
version: '0.21.25',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
'cpp_std=c++14'
|
||||
'cpp_std=c++14',
|
||||
'warning_level=2',
|
||||
],
|
||||
license: 'GPLv2+',
|
||||
)
|
||||
@@ -40,9 +41,6 @@ common_cxxflags = [
|
||||
]
|
||||
|
||||
test_common_flags = [
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
|
||||
'-fvisibility=hidden',
|
||||
|
||||
'-ffast-math',
|
||||
@@ -142,7 +140,13 @@ conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
|
||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
|
||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
|
||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
|
||||
conf.set('HAVE_STRNDUP', compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
|
||||
|
||||
# Explicitly exclude Windows in this check because
|
||||
# https://github.com/mesonbuild/meson/issues/3672 (reported in 2018,
|
||||
# still not fixed in 2020) causes Meson to believe it exists, because
|
||||
# __builtin_strndup() exists (but strndup() still cannot be used).
|
||||
conf.set('HAVE_STRNDUP', not is_windows and compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
|
||||
|
||||
conf.set('HAVE_STRCASESTR', compiler.has_function('strcasestr'))
|
||||
|
||||
conf.set('HAVE_PRCTL', is_linux)
|
||||
@@ -290,6 +294,7 @@ if not is_android
|
||||
else
|
||||
sources += [
|
||||
'src/android/Context.cxx',
|
||||
'src/android/AudioManager.cxx',
|
||||
'src/android/Environment.cxx',
|
||||
'src/android/LogListener.cxx',
|
||||
]
|
||||
@@ -311,8 +316,11 @@ subdir('src/util')
|
||||
subdir('src/time')
|
||||
subdir('src/system')
|
||||
subdir('src/thread')
|
||||
subdir('src/net')
|
||||
subdir('src/event')
|
||||
|
||||
subdir('src/apple')
|
||||
|
||||
subdir('src/lib/dbus')
|
||||
subdir('src/lib/icu')
|
||||
subdir('src/lib/smbclient')
|
||||
@@ -335,7 +343,6 @@ subdir('src/lib/yajl')
|
||||
|
||||
subdir('src/fs')
|
||||
subdir('src/config')
|
||||
subdir('src/net')
|
||||
subdir('src/tag')
|
||||
subdir('src/pcm')
|
||||
subdir('src/neighbor')
|
||||
|
||||
45
python/build/cmake.py
Normal file
45
python/build/cmake.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
|
||||
def configure(toolchain, src, build, args=()):
|
||||
cross_args = []
|
||||
|
||||
if toolchain.is_windows:
|
||||
cross_args.append('-DCMAKE_SYSTEM_NAME=Windows')
|
||||
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres)
|
||||
|
||||
configure = [
|
||||
'cmake',
|
||||
src,
|
||||
|
||||
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
|
||||
'-DCMAKE_BUILD_TYPE=release',
|
||||
|
||||
'-DCMAKE_C_COMPILER=' + toolchain.cc,
|
||||
'-DCMAKE_CXX_COMPILER=' + toolchain.cxx,
|
||||
|
||||
'-DCMAKE_C_FLAGS=' + toolchain.cflags + ' ' + toolchain.cppflags,
|
||||
'-DCMAKE_CXX_FLAGS=' + toolchain.cxxflags + ' ' + toolchain.cppflags,
|
||||
|
||||
'-GNinja',
|
||||
] + cross_args + args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env, cwd=build)
|
||||
|
||||
class CmakeProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
configure(toolchain, src, build, self.configure_args)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
@@ -4,13 +4,14 @@ from os.path import abspath
|
||||
from build.project import Project
|
||||
from build.zlib import ZlibProject
|
||||
from build.meson import MesonProject
|
||||
from build.cmake import CmakeProject
|
||||
from build.autotools import AutotoolsProject
|
||||
from build.ffmpeg import FfmpegProject
|
||||
from build.boost import BoostProject
|
||||
|
||||
libmpdclient = MesonProject(
|
||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.17.tar.xz',
|
||||
'ee9b8f1c7e95b65c8f18a354daf7b16bfcd455fc52a0f3b5abe402316bce3559',
|
||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz',
|
||||
'4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa',
|
||||
'lib/libmpdclient.a',
|
||||
)
|
||||
|
||||
@@ -111,9 +112,44 @@ liblame = AutotoolsProject(
|
||||
],
|
||||
)
|
||||
|
||||
libmodplug = AutotoolsProject(
|
||||
'https://downloads.sourceforge.net/modplug-xmms/libmodplug/0.8.9.0/libmodplug-0.8.9.0.tar.gz',
|
||||
'457ca5a6c179656d66c01505c0d95fafaead4329b9dbaa0f997d00a3508ad9de',
|
||||
'lib/libmodplug.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3',
|
||||
'498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5',
|
||||
'lib/libWildMidi.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DWANT_PLAYER=OFF',
|
||||
'-DWANT_STATIC=ON',
|
||||
],
|
||||
base='wildmidi-wildmidi-0.4.3',
|
||||
name='wildmidi',
|
||||
version='0.4.3',
|
||||
)
|
||||
|
||||
gme = CmakeProject(
|
||||
'https://bitbucket.org/mpyne/game-music-emu/downloads/game-music-emu-0.6.3.tar.xz',
|
||||
'aba34e53ef0ec6a34b58b84e28bf8cfbccee6585cebca25333604c35db3e051d',
|
||||
'lib/libgme.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DENABLE_UBSAN=OFF',
|
||||
'-DZLIB_INCLUDE_DIR=OFF',
|
||||
'-DSDL2_DIR=OFF',
|
||||
],
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.2.2.tar.xz',
|
||||
'cb754255ab0ee2ea5f66f8850e1bd6ad5cac1cd855d0a2f4990fb8c668b0d29c',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.2.3.tar.xz',
|
||||
'9df6c90aed1337634c1fb026fb01c154c29c82a64ea71291ff2da9aacb9aad31',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -341,8 +377,8 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.68.0.tar.xz',
|
||||
'b724240722276a27f6e770b952121a3afd097129d8c9fe18e6272dc34192035a',
|
||||
'http://curl.haxx.se/download/curl-7.70.0.tar.xz',
|
||||
'032f43f2674008c761af19bf536374128c16241fb234699a55f9fb603fcfbae7',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -397,7 +433,7 @@ libnfs = AutotoolsProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2',
|
||||
'59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722',
|
||||
'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2',
|
||||
'4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
||||
@@ -91,7 +91,12 @@ def configure(toolchain, src, build, args=()):
|
||||
'--cross-file', cross_file,
|
||||
] + args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env)
|
||||
env = toolchain.env.copy()
|
||||
|
||||
# Meson 0.54 requires the BOOST_ROOT environment variable
|
||||
env['BOOST_ROOT'] = toolchain.install_prefix
|
||||
|
||||
subprocess.check_call(configure, env=env)
|
||||
|
||||
class MesonProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_AUDIO_FORMAT_HXX
|
||||
#define MPD_AUDIO_FORMAT_HXX
|
||||
|
||||
#include "pcm/SampleFormat.hxx"
|
||||
#include "pcm/SampleFormat.hxx" // IWYU pragma: export
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
#include "playlist/PlaylistRegistry.hxx"
|
||||
#include "playlist/PlaylistPlugin.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/NarrowPath.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/OptionDef.hxx"
|
||||
@@ -380,17 +380,7 @@ ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
|
||||
if (config_file != nullptr) {
|
||||
/* use specified configuration file */
|
||||
#ifdef _UNICODE
|
||||
wchar_t buffer[MAX_PATH];
|
||||
auto result = MultiByteToWideChar(CP_ACP, 0, config_file, -1,
|
||||
buffer, ARRAY_SIZE(buffer));
|
||||
if (result <= 0)
|
||||
throw MakeLastError("MultiByteToWideChar() failed");
|
||||
|
||||
ReadConfigFile(config, Path::FromFS(buffer));
|
||||
#else
|
||||
ReadConfigFile(config, Path::FromFS(config_file));
|
||||
#endif
|
||||
ReadConfigFile(config, FromNarrowPath(config_file));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
82
src/Log.cxx
82
src/Log.cxx
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -19,8 +19,7 @@
|
||||
|
||||
#include "LogV.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
#include <exception>
|
||||
#include "util/Exception.hxx"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -29,20 +28,20 @@
|
||||
static constexpr Domain exception_domain("exception");
|
||||
|
||||
void
|
||||
LogFormatV(const Domain &domain, LogLevel level,
|
||||
LogFormatV(LogLevel level, const Domain &domain,
|
||||
const char *fmt, va_list ap) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
Log(domain, level, msg);
|
||||
Log(level, domain, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept
|
||||
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, level, fmt, ap);
|
||||
LogFormatV(level, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -51,7 +50,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
|
||||
LogFormatV(LogLevel::DEBUG, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::INFO, fmt, ap);
|
||||
LogFormatV(LogLevel::INFO, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -69,7 +68,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::DEFAULT, fmt, ap);
|
||||
LogFormatV(LogLevel::DEFAULT, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -78,7 +77,7 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::WARNING, fmt, ap);
|
||||
LogFormatV(LogLevel::WARNING, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -87,42 +86,24 @@ FormatError(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::ERROR, fmt, ap);
|
||||
LogFormatV(LogLevel::ERROR, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e) noexcept
|
||||
Log(LogLevel level, const std::exception &e) noexcept
|
||||
{
|
||||
Log(exception_domain, LogLevel::ERROR, e.what());
|
||||
|
||||
try {
|
||||
std::rethrow_if_nested(e);
|
||||
} catch (const std::exception &nested) {
|
||||
LogError(nested, "nested");
|
||||
} catch (...) {
|
||||
Log(exception_domain, LogLevel::ERROR,
|
||||
"Unrecognized nested exception");
|
||||
}
|
||||
Log(level, exception_domain, GetFullMessage(e).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e, const char *msg) noexcept
|
||||
Log(LogLevel level, const std::exception &e, const char *msg) noexcept
|
||||
{
|
||||
FormatError(exception_domain, "%s: %s", msg, e.what());
|
||||
|
||||
try {
|
||||
std::rethrow_if_nested(e);
|
||||
} catch (const std::exception &nested) {
|
||||
LogError(nested);
|
||||
} catch (...) {
|
||||
Log(exception_domain, LogLevel::ERROR,
|
||||
"Unrecognized nested exception");
|
||||
}
|
||||
LogFormat(level, exception_domain, "%s: %s", msg, GetFullMessage(e).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
||||
LogFormat(LogLevel level, const std::exception &e, const char *fmt, ...) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
va_list ap;
|
||||
@@ -130,37 +111,24 @@ FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
LogError(e, msg);
|
||||
Log(level, e, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep) noexcept
|
||||
Log(LogLevel level, const std::exception_ptr &ep) noexcept
|
||||
{
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e);
|
||||
} catch (...) {
|
||||
Log(exception_domain, LogLevel::ERROR,
|
||||
"Unrecognized exception");
|
||||
}
|
||||
Log(level, exception_domain, GetFullMessage(ep).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
||||
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
|
||||
{
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e, msg);
|
||||
} catch (...) {
|
||||
FormatError(exception_domain,
|
||||
"%s: Unrecognized exception", msg);
|
||||
}
|
||||
LogFormat(level, exception_domain, "%s: %s", msg,
|
||||
GetFullMessage(ep).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||
LogFormat(LogLevel level, const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
va_list ap;
|
||||
@@ -168,13 +136,13 @@ FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
LogError(ep, msg);
|
||||
Log(level, ep, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogErrno(const Domain &domain, int e, const char *msg) noexcept
|
||||
{
|
||||
LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
|
||||
LogFormat(LogLevel::ERROR, domain, "%s: %s", msg, strerror(e));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
85
src/Log.hxx
85
src/Log.hxx
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -28,16 +28,38 @@
|
||||
class Domain;
|
||||
|
||||
void
|
||||
Log(const Domain &domain, LogLevel level, const char *msg) noexcept;
|
||||
Log(LogLevel level, const Domain &domain, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept;
|
||||
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception &e,
|
||||
const char *fmt, ...) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception_ptr &ep,
|
||||
const char *fmt, ...) noexcept;
|
||||
|
||||
static inline void
|
||||
LogDebug(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::DEBUG, msg);
|
||||
Log(LogLevel::DEBUG, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -47,7 +69,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogInfo(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::INFO, msg);
|
||||
Log(LogLevel::INFO, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -57,7 +79,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogDefault(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::DEFAULT, msg);
|
||||
Log(LogLevel::DEFAULT, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -67,7 +89,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogWarning(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::WARNING, msg);
|
||||
Log(LogLevel::WARNING, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -77,28 +99,47 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogError(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::ERROR, msg);
|
||||
Log(LogLevel::ERROR, domain, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception &e) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, e);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e, const char *msg) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception &e, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, e, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept;
|
||||
template<typename... Args>
|
||||
inline void
|
||||
FormatError(const std::exception &e, const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
LogFormat(LogLevel::ERROR, e, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception_ptr &ep) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, ep);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, ep, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept;
|
||||
template<typename... Args>
|
||||
inline void
|
||||
FormatError(const std::exception_ptr &ep,
|
||||
const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
LogFormat(LogLevel::ERROR, ep, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -176,7 +176,7 @@ FileLog(const Domain &domain, const char *message) noexcept
|
||||
#endif /* !ANDROID */
|
||||
|
||||
void
|
||||
Log(const Domain &domain, LogLevel level, const char *msg) noexcept
|
||||
Log(LogLevel level, const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <stdarg.h>
|
||||
|
||||
void
|
||||
LogFormatV(const Domain &domain, LogLevel level,
|
||||
LogFormatV(LogLevel level, const Domain &domain,
|
||||
const char *fmt, va_list ap) noexcept;
|
||||
|
||||
#endif /* LOG_H */
|
||||
|
||||
@@ -460,7 +460,7 @@ MainOrThrow(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
const unsigned max_clients =
|
||||
raw_config.GetPositive(ConfigOption::MAX_CONN, 10);
|
||||
raw_config.GetPositive(ConfigOption::MAX_CONN, 100);
|
||||
instance->client_list = new ClientList(max_clients);
|
||||
|
||||
initialize_decoder_and_player(raw_config, config.replay_gain);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2020 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -27,10 +27,20 @@
|
||||
* that this plugin is unavailable. It will be disabled, and MPD can
|
||||
* continue initialization.
|
||||
*/
|
||||
class PluginUnavailable final : public std::runtime_error {
|
||||
class PluginUnavailable : public std::runtime_error {
|
||||
public:
|
||||
explicit PluginUnavailable(const char *msg)
|
||||
:std::runtime_error(msg) {}
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Like #PluginUnavailable, but denotes that the plugin is not
|
||||
* available because it was not explicitly enabled in the
|
||||
* configuration. The message may describe the necessary steps to
|
||||
* enable it.
|
||||
*/
|
||||
class PluginUnconfigured : public PluginUnavailable {
|
||||
public:
|
||||
using PluginUnavailable::PluginUnavailable;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -79,17 +79,22 @@ Song::UpdateFile(Storage &storage) noexcept
|
||||
TagBuilder tag_builder;
|
||||
auto new_audio_format = AudioFormat::Undefined();
|
||||
|
||||
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
||||
if (path_fs.IsNull()) {
|
||||
const auto absolute_uri =
|
||||
storage.MapUTF8(relative_uri.c_str());
|
||||
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
|
||||
&new_audio_format))
|
||||
return false;
|
||||
} else {
|
||||
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
|
||||
try {
|
||||
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
||||
if (path_fs.IsNull()) {
|
||||
const auto absolute_uri =
|
||||
storage.MapUTF8(relative_uri.c_str());
|
||||
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
|
||||
&new_audio_format))
|
||||
return false;
|
||||
return false;
|
||||
} else {
|
||||
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
|
||||
&new_audio_format))
|
||||
return false;
|
||||
}
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
return false;
|
||||
}
|
||||
|
||||
mtime = info.mtime;
|
||||
@@ -153,8 +158,14 @@ DetachedSong::LoadFile(Path path) noexcept
|
||||
return false;
|
||||
|
||||
TagBuilder tag_builder;
|
||||
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
||||
|
||||
try {
|
||||
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
||||
return false;
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
return false;
|
||||
}
|
||||
|
||||
mtime = fi.GetModificationTime();
|
||||
tag_builder.Commit(tag);
|
||||
@@ -173,8 +184,14 @@ DetachedSong::Update() noexcept
|
||||
return LoadFile(path_fs);
|
||||
} else if (IsRemote()) {
|
||||
TagBuilder tag_builder;
|
||||
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
||||
|
||||
try {
|
||||
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
||||
return false;
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
return false;
|
||||
}
|
||||
|
||||
mtime = std::chrono::system_clock::time_point::min();
|
||||
tag_builder.Commit(tag);
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#include "system/Clock.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/Math.hxx"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
#ifndef _WIN32
|
||||
/**
|
||||
@@ -121,7 +121,7 @@ stats_print(Response &r, const Partition &partition)
|
||||
#else
|
||||
(unsigned)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count(),
|
||||
#endif
|
||||
std::lround(partition.pc.GetTotalPlayTime().count()));
|
||||
lround(partition.pc.GetTotalPlayTime().count()));
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
const Database *db = partition.instance.GetDatabase();
|
||||
|
||||
@@ -47,11 +47,11 @@ public:
|
||||
handler(_handler),
|
||||
is(nullptr) {}
|
||||
|
||||
bool ScanFile(const DecoderPlugin &plugin) noexcept {
|
||||
bool ScanFile(const DecoderPlugin &plugin) {
|
||||
return plugin.ScanFile(path_fs, handler);
|
||||
}
|
||||
|
||||
bool ScanStream(const DecoderPlugin &plugin) noexcept {
|
||||
bool ScanStream(const DecoderPlugin &plugin) {
|
||||
if (plugin.scan_stream == nullptr)
|
||||
return false;
|
||||
|
||||
@@ -73,14 +73,14 @@ public:
|
||||
return plugin.ScanStream(*is, handler);
|
||||
}
|
||||
|
||||
bool Scan(const DecoderPlugin &plugin) noexcept {
|
||||
bool Scan(const DecoderPlugin &plugin) {
|
||||
return plugin.SupportsSuffix(suffix) &&
|
||||
(ScanFile(plugin) || ScanStream(plugin));
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
||||
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler)
|
||||
{
|
||||
assert(!path_fs.IsNull());
|
||||
|
||||
@@ -100,7 +100,7 @@ ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
||||
|
||||
bool
|
||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||
AudioFormat *audio_format) noexcept
|
||||
AudioFormat *audio_format)
|
||||
{
|
||||
FullTagHandler h(builder, audio_format);
|
||||
|
||||
|
||||
@@ -30,22 +30,26 @@ class TagBuilder;
|
||||
* but does not fall back to generic scanners (APE and ID3) if no tags
|
||||
* were found (but the file was recognized).
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
ScanFileTagsNoGeneric(Path path, TagHandler &handler) noexcept;
|
||||
ScanFileTagsNoGeneric(Path path, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Scan the tags of a song file. Invokes matching decoder plugins,
|
||||
* and falls back to generic scanners (APE and ID3) if no tags were
|
||||
* found (but the file was recognized).
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||
AudioFormat *audio_format=nullptr) noexcept;
|
||||
AudioFormat *audio_format=nullptr);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,7 +45,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
|
||||
}
|
||||
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
@@ -73,19 +73,17 @@ tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagHandler &handler) noexcept
|
||||
try {
|
||||
tag_stream_scan(const char *uri, TagHandler &handler)
|
||||
{
|
||||
Mutex mutex;
|
||||
|
||||
auto is = InputStream::OpenReady(uri, mutex);
|
||||
return tag_stream_scan(*is, handler);
|
||||
} catch (const std::exception &e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||
AudioFormat *audio_format) noexcept
|
||||
AudioFormat *audio_format)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
@@ -102,12 +100,10 @@ tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||
AudioFormat *audio_format) noexcept
|
||||
try {
|
||||
AudioFormat *audio_format)
|
||||
{
|
||||
Mutex mutex;
|
||||
|
||||
auto is = InputStream::OpenReady(uri, mutex);
|
||||
return tag_stream_scan(*is, builder, audio_format);
|
||||
} catch (const std::exception &e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,29 +29,39 @@ class TagBuilder;
|
||||
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||
* plugins, but does not invoke the special "APE" and "ID3" scanners.
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept;
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Throws on I/O error.
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagHandler &handler) noexcept;
|
||||
tag_stream_scan(const char *uri, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||
* plugins, and falls back to generic scanners (APE and ID3) if no
|
||||
* tags were found (but the file was recognized).
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||
AudioFormat *audio_format=nullptr) noexcept;
|
||||
AudioFormat *audio_format=nullptr);
|
||||
|
||||
/**
|
||||
* Throws on I/O error.
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||
AudioFormat *audio_format=nullptr) noexcept;
|
||||
AudioFormat *audio_format=nullptr);
|
||||
|
||||
#endif
|
||||
|
||||
56
src/android/AudioManager.cxx
Normal file
56
src/android/AudioManager.cxx
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2003-2020 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "AudioManager.hxx"
|
||||
#include "java/Class.hxx"
|
||||
#include "java/Exception.hxx"
|
||||
#include "java/File.hxx"
|
||||
|
||||
#define STREAM_MUSIC 3
|
||||
|
||||
AudioManager::AudioManager(JNIEnv *env, jobject obj) noexcept
|
||||
: Java::GlobalObject(env, obj)
|
||||
{
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
jmethodID method = env->GetMethodID(cls, "getStreamMaxVolume", "(I)I");
|
||||
assert(method);
|
||||
maxVolume = env->CallIntMethod(Get(), method, STREAM_MUSIC);
|
||||
|
||||
getStreamVolumeMethod = env->GetMethodID(cls, "getStreamVolume", "(I)I");
|
||||
assert(getStreamVolumeMethod);
|
||||
|
||||
setStreamVolumeMethod = env->GetMethodID(cls, "setStreamVolume", "(III)V");
|
||||
assert(setStreamVolumeMethod);
|
||||
}
|
||||
|
||||
int
|
||||
AudioManager::GetVolume(JNIEnv *env)
|
||||
{
|
||||
if (maxVolume == 0)
|
||||
return 0;
|
||||
return env->CallIntMethod(Get(), getStreamVolumeMethod, STREAM_MUSIC);
|
||||
}
|
||||
|
||||
void
|
||||
AudioManager::SetVolume(JNIEnv *env, int volume)
|
||||
{
|
||||
if (maxVolume == 0)
|
||||
return;
|
||||
env->CallVoidMethod(Get(), setStreamVolumeMethod, STREAM_MUSIC, volume, 0);
|
||||
}
|
||||
42
src/android/AudioManager.hxx
Normal file
42
src/android/AudioManager.hxx
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2003-2020 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_ANDROID_AUDIO_MANAGER_HXX
|
||||
#define MPD_ANDROID_AUDIO_MANAGER_HXX
|
||||
|
||||
#include "java/Object.hxx"
|
||||
|
||||
class AudioManager : public Java::GlobalObject {
|
||||
int maxVolume;
|
||||
jmethodID getStreamVolumeMethod;
|
||||
jmethodID setStreamVolumeMethod;
|
||||
|
||||
public:
|
||||
AudioManager(JNIEnv *env, jobject obj) noexcept;
|
||||
|
||||
AudioManager(std::nullptr_t) noexcept { maxVolume = 0; }
|
||||
|
||||
~AudioManager() noexcept {}
|
||||
|
||||
int GetMaxVolume() { return maxVolume; }
|
||||
int GetVolume(JNIEnv *env);
|
||||
void SetVolume(JNIEnv *env, int);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -20,10 +20,13 @@
|
||||
#include "Context.hxx"
|
||||
#include "java/Class.hxx"
|
||||
#include "java/File.hxx"
|
||||
#include "java/String.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
||||
#include "AudioManager.hxx"
|
||||
|
||||
AllocatedPath
|
||||
Context::GetCacheDir(JNIEnv *env) const
|
||||
Context::GetCacheDir(JNIEnv *env) const noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
@@ -40,3 +43,21 @@ Context::GetCacheDir(JNIEnv *env) const
|
||||
|
||||
return Java::File::ToAbsolutePath(env, file);
|
||||
}
|
||||
|
||||
AudioManager *
|
||||
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());
|
||||
if (Java::DiscardException(env) || am == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return new AudioManager(env, am);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -23,13 +23,18 @@
|
||||
#include "java/Object.hxx"
|
||||
|
||||
class AllocatedPath;
|
||||
class AudioManager;
|
||||
|
||||
class Context : public Java::Object {
|
||||
class Context : public Java::GlobalObject {
|
||||
public:
|
||||
Context(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
|
||||
Context(JNIEnv *env, jobject obj) noexcept
|
||||
:Java::GlobalObject(env, obj) {}
|
||||
|
||||
gcc_pure
|
||||
AllocatedPath GetCacheDir(JNIEnv *env) const;
|
||||
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
||||
|
||||
gcc_pure
|
||||
AudioManager *GetAudioManager(JNIEnv *env) noexcept;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
#include "java/Object.hxx"
|
||||
|
||||
class LogListener : public Java::Object {
|
||||
class LogListener : public Java::GlobalObject {
|
||||
public:
|
||||
LogListener(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
|
||||
LogListener(JNIEnv *env, jobject obj):Java::GlobalObject(env, obj) {}
|
||||
|
||||
void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const;
|
||||
};
|
||||
|
||||
40
src/apple/AudioObject.cxx
Normal file
40
src/apple/AudioObject.cxx
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 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 "AudioObject.hxx"
|
||||
#include "StringRef.hxx"
|
||||
|
||||
Apple::StringRef
|
||||
AudioObjectGetStringProperty(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
auto s = AudioObjectGetPropertyDataT<CFStringRef>(inObjectID,
|
||||
inAddress);
|
||||
return Apple::StringRef(s);
|
||||
}
|
||||
105
src/apple/AudioObject.hxx
Normal file
105
src/apple/AudioObject.hxx
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2020 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 APPLE_AUDIO_OBJECT_HXX
|
||||
#define APPLE_AUDIO_OBJECT_HXX
|
||||
|
||||
#include "Throw.hxx"
|
||||
#include "util/AllocatedArray.hxx"
|
||||
|
||||
#include <CoreAudio/AudioHardware.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Apple {
|
||||
class StringRef;
|
||||
}
|
||||
|
||||
inline std::size_t
|
||||
AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
UInt32 size;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(inObjectID,
|
||||
&inAddress,
|
||||
0, nullptr, &size);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
AudioObjectGetPropertyDataT(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
OSStatus status;
|
||||
UInt32 size = sizeof(T);
|
||||
T value;
|
||||
|
||||
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
|
||||
0, nullptr,
|
||||
&size, &value);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
Apple::StringRef
|
||||
AudioObjectGetStringProperty(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress);
|
||||
|
||||
template<typename T>
|
||||
AllocatedArray<T>
|
||||
AudioObjectGetPropertyDataArray(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
OSStatus status;
|
||||
UInt32 size;
|
||||
|
||||
status = AudioObjectGetPropertyDataSize(inObjectID,
|
||||
&inAddress,
|
||||
0, nullptr, &size);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
AllocatedArray<T> result(size / sizeof(T));
|
||||
|
||||
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
|
||||
0, nullptr,
|
||||
&size, result.begin());
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
111
src/apple/AudioUnit.hxx
Normal file
111
src/apple/AudioUnit.hxx
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2020 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 APPLE_AUDIO_UNIT_HXX
|
||||
#define APPLE_AUDIO_UNIT_HXX
|
||||
|
||||
#include "Throw.hxx"
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
AudioUnitGetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
|
||||
AudioUnitScope inScope,
|
||||
AudioUnitElement inElement)
|
||||
{
|
||||
UInt32 size = sizeof(T);
|
||||
T value;
|
||||
|
||||
OSStatus status = AudioUnitGetProperty(inUnit, inID, inScope,
|
||||
inElement,
|
||||
&value, &size);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
AudioUnitSetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
|
||||
AudioUnitScope inScope,
|
||||
AudioUnitElement inElement,
|
||||
const T &value)
|
||||
{
|
||||
OSStatus status = AudioUnitSetProperty(inUnit, inID, inScope,
|
||||
inElement,
|
||||
&value, sizeof(value));
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetCurrentDevice(AudioUnit inUnit, const AudioDeviceID &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetInputStreamFormat(AudioUnit inUnit,
|
||||
const AudioStreamBasicDescription &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetInputRenderCallback(AudioUnit inUnit,
|
||||
const AURenderCallbackStruct &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
inline UInt32
|
||||
AudioUnitGetBufferFrameSize(AudioUnit inUnit)
|
||||
{
|
||||
return AudioUnitGetPropertyT<UInt32>(inUnit,
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioUnitScope_Global, 0);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetBufferFrameSize(AudioUnit inUnit, const UInt32 &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioUnitScope_Global, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
#endif
|
||||
75
src/apple/ErrorRef.hxx
Normal file
75
src/apple/ErrorRef.hxx
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 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 APPLE_ERROR_REF_HXX
|
||||
#define APPLE_ERROR_REF_HXX
|
||||
|
||||
#include <CoreFoundation/CFError.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
class ErrorRef {
|
||||
CFErrorRef ref = nullptr;
|
||||
|
||||
public:
|
||||
explicit ErrorRef(CFErrorRef _ref) noexcept
|
||||
:ref(_ref) {}
|
||||
|
||||
ErrorRef(CFAllocatorRef allocator, CFErrorDomain domain,
|
||||
CFIndex code, CFDictionaryRef userInfo) noexcept
|
||||
:ref(CFErrorCreate(allocator, domain, code, userInfo)) {}
|
||||
|
||||
ErrorRef(ErrorRef &&src) noexcept
|
||||
:ref(std::exchange(src.ref, nullptr)) {}
|
||||
|
||||
~ErrorRef() noexcept {
|
||||
if (ref)
|
||||
CFRelease(ref);
|
||||
}
|
||||
|
||||
ErrorRef &operator=(ErrorRef &&src) noexcept {
|
||||
using std::swap;
|
||||
swap(ref, src.ref);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const noexcept {
|
||||
return ref != nullptr;
|
||||
}
|
||||
|
||||
CFStringRef CopyDescription() const noexcept {
|
||||
return CFErrorCopyDescription(ref);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Apple
|
||||
|
||||
#endif
|
||||
73
src/apple/StringRef.hxx
Normal file
73
src/apple/StringRef.hxx
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2020 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 APPLE_STRING_REF_HXX
|
||||
#define APPLE_STRING_REF_HXX
|
||||
|
||||
#include <CoreFoundation/CFString.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
class StringRef {
|
||||
CFStringRef ref = nullptr;
|
||||
|
||||
public:
|
||||
explicit StringRef(CFStringRef _ref) noexcept
|
||||
:ref(_ref) {}
|
||||
|
||||
StringRef(StringRef &&src) noexcept
|
||||
:ref(std::exchange(src.ref, nullptr)) {}
|
||||
|
||||
~StringRef() noexcept {
|
||||
if (ref)
|
||||
CFRelease(ref);
|
||||
}
|
||||
|
||||
StringRef &operator=(StringRef &&src) noexcept {
|
||||
using std::swap;
|
||||
swap(ref, src.ref);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const noexcept {
|
||||
return ref != nullptr;
|
||||
}
|
||||
|
||||
bool GetCString(char *buffer, std::size_t size,
|
||||
CFStringEncoding encoding=kCFStringEncodingUTF8) const noexcept
|
||||
{
|
||||
return CFStringGetCString(ref, buffer, size, encoding);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Apple
|
||||
|
||||
#endif
|
||||
67
src/apple/Throw.cxx
Normal file
67
src/apple/Throw.cxx
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2020 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 "Throw.hxx"
|
||||
#include "ErrorRef.hxx"
|
||||
#include "StringRef.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status)
|
||||
{
|
||||
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
|
||||
status, nullptr);
|
||||
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||
|
||||
char msg[1024];
|
||||
if (!cfstr.GetCString(msg, sizeof(msg)))
|
||||
throw std::runtime_error("Unknown OSStatus");
|
||||
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status, const char *_msg)
|
||||
{
|
||||
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
|
||||
status, nullptr);
|
||||
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||
|
||||
char msg[1024];
|
||||
strcpy(msg, _msg);
|
||||
size_t length = strlen(msg);
|
||||
|
||||
cfstr.GetCString(msg + length, sizeof(msg) - length);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
} // namespace Apple
|
||||
45
src/apple/Throw.hxx
Normal file
45
src/apple/Throw.hxx
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2020 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 APPLE_THROW_HXX
|
||||
#define APPLE_THROW_HXX
|
||||
|
||||
#include <CoreFoundation/CFBase.h>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status);
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status, const char *msg);
|
||||
|
||||
} // namespace Apple
|
||||
|
||||
#endif
|
||||
28
src/apple/meson.build
Normal file
28
src/apple/meson.build
Normal file
@@ -0,0 +1,28 @@
|
||||
if not is_darwin
|
||||
apple_dep = dependency('', required: false)
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
audiounit_dep = declare_dependency(
|
||||
link_args: ['-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices'],
|
||||
dependencies: [
|
||||
boost_dep,
|
||||
],
|
||||
)
|
||||
|
||||
apple = static_library(
|
||||
'apple',
|
||||
'AudioObject.cxx',
|
||||
'Throw.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
audiounit_dep,
|
||||
],
|
||||
)
|
||||
|
||||
apple_dep = declare_dependency(
|
||||
link_with: apple,
|
||||
dependencies: [
|
||||
audiounit_dep,
|
||||
],
|
||||
)
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
|
||||
#include <cdio/iso9660.h>
|
||||
|
||||
@@ -93,7 +94,10 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
||||
auto *statbuf = (iso9660_stat_t *)
|
||||
_cdio_list_node_data(entnode);
|
||||
const char *filename = statbuf->filename;
|
||||
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
|
||||
if (StringIsEmpty(filename) ||
|
||||
PathTraitsUTF8::IsSpecialFilename(filename))
|
||||
/* skip empty names (libcdio bug?) */
|
||||
/* skip special names like "." and ".." */
|
||||
continue;
|
||||
|
||||
size_t filename_length = strlen(filename);
|
||||
|
||||
@@ -193,7 +193,7 @@ static constexpr struct command commands[] = {
|
||||
{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
|
||||
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
|
||||
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
|
||||
{ "tagtypes", PERMISSION_READ, 0, -1, handle_tagtypes },
|
||||
{ "tagtypes", PERMISSION_NONE, 0, -1, handle_tagtypes },
|
||||
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
|
||||
#ifdef ENABLE_DATABASE
|
||||
{ "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },
|
||||
|
||||
@@ -34,13 +34,12 @@
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/Exception.hxx"
|
||||
#include "util/Math.hxx"
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
#include "db/update/Service.hxx"
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#define COMMAND_STATUS_STATE "state"
|
||||
#define COMMAND_STATUS_REPEAT "repeat"
|
||||
#define COMMAND_STATUS_SINGLE "single"
|
||||
@@ -154,7 +153,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
||||
|
||||
if (pc.GetCrossFade() > FloatDuration::zero())
|
||||
r.Format(COMMAND_STATUS_CROSSFADE ": %lu\n",
|
||||
std::lround(pc.GetCrossFade().count()));
|
||||
lround(pc.GetCrossFade().count()));
|
||||
|
||||
if (pc.GetMixRampDelay() > FloatDuration::zero())
|
||||
r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n",
|
||||
@@ -173,7 +172,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
||||
COMMAND_STATUS_BITRATE ": %u\n",
|
||||
player_status.elapsed_time.RoundS(),
|
||||
player_status.total_time.IsNegative()
|
||||
? 0u
|
||||
? 0U
|
||||
: unsigned(player_status.total_time.RoundS()),
|
||||
player_status.elapsed_time.ToDoubleS(),
|
||||
player_status.bit_rate);
|
||||
|
||||
@@ -198,6 +198,16 @@ handle_mount(Client &client, Request args, Response &r)
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
if (composite.IsMountPoint(local_uri)) {
|
||||
r.Error(ACK_ERROR_ARG, "Mount point busy");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
if (composite.IsMounted(remote_uri)) {
|
||||
r.Error(ACK_ERROR_ARG, "This storage is already mounted");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
auto &event_loop = instance.io_thread.GetEventLoop();
|
||||
auto storage = CreateStorageURI(event_loop, remote_uri);
|
||||
if (storage == nullptr) {
|
||||
@@ -210,8 +220,10 @@ handle_mount(Client &client, Request args, Response &r)
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
||||
bool need_update;
|
||||
|
||||
try {
|
||||
db->Mount(local_uri, remote_uri);
|
||||
need_update = !db->Mount(local_uri, remote_uri);
|
||||
} catch (...) {
|
||||
composite.Unmount(local_uri);
|
||||
throw;
|
||||
@@ -220,6 +232,12 @@ handle_mount(Client &client, Request args, Response &r)
|
||||
// TODO: call Instance::OnDatabaseModified()?
|
||||
// TODO: trigger database update?
|
||||
instance.EmitIdle(IDLE_DATABASE);
|
||||
|
||||
if (need_update) {
|
||||
UpdateService *update = client.GetInstance().update;
|
||||
if (update != nullptr)
|
||||
update->Enqueue(local_uri, false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -448,7 +448,7 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
||||
listener(_listener),
|
||||
host(block.GetBlockValue("host", "")),
|
||||
password(block.GetBlockValue("password", "")),
|
||||
port(block.GetBlockValue("port", 0u)),
|
||||
port(block.GetBlockValue("port", 0U)),
|
||||
keepalive(block.GetBlockValue("keepalive", false))
|
||||
{
|
||||
}
|
||||
@@ -493,9 +493,13 @@ ProxyDatabase::Connect()
|
||||
try {
|
||||
CheckError(connection);
|
||||
|
||||
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0)
|
||||
throw FormatRuntimeError("Connect to MPD %s, but this plugin requires at least version 0.19",
|
||||
mpd_connection_get_server_version(connection));
|
||||
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0) {
|
||||
const unsigned *version =
|
||||
mpd_connection_get_server_version(connection);
|
||||
throw FormatRuntimeError("Connect to MPD %u.%u.%u, but this "
|
||||
"plugin requires at least version 0.19",
|
||||
version[0], version[1], version[2]);
|
||||
}
|
||||
|
||||
if (!password.empty() &&
|
||||
!mpd_run_password(connection, password.c_str()))
|
||||
@@ -517,7 +521,7 @@ ProxyDatabase::Connect()
|
||||
(void)keepalive;
|
||||
#endif
|
||||
|
||||
idle_received = ~0u;
|
||||
idle_received = ~0U;
|
||||
is_idle = false;
|
||||
|
||||
SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "fs/Traits.hxx"
|
||||
#include "util/Alloc.hxx"
|
||||
#include "util/DeleteDisposer.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -69,7 +70,15 @@ Directory::GetName() const noexcept
|
||||
{
|
||||
assert(!IsRoot());
|
||||
|
||||
return PathTraitsUTF8::GetBase(path.c_str());
|
||||
if (parent->IsRoot())
|
||||
return path.c_str();
|
||||
|
||||
assert(StringAfterPrefix(path.c_str(), parent->path.c_str()) != nullptr);
|
||||
assert(*StringAfterPrefix(path.c_str(), parent->path.c_str()) == PathTraitsUTF8::SEPARATOR);
|
||||
|
||||
/* strip the parent directory path and the slash separator
|
||||
from this directory's path, and the base name remains */
|
||||
return path.c_str() + parent->path.length() + 1;
|
||||
}
|
||||
|
||||
Directory *
|
||||
|
||||
@@ -428,7 +428,7 @@ IsUnsafeChar(char ch)
|
||||
return !IsSafeChar(ch);
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||
{
|
||||
if (cache_path.IsNull())
|
||||
@@ -447,14 +447,11 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||
compress);
|
||||
db->Open();
|
||||
|
||||
// TODO: update the new database instance?
|
||||
bool exists = db->FileExists();
|
||||
|
||||
try {
|
||||
Mount(local_uri, std::move(db));
|
||||
} catch (...) {
|
||||
db->Close();
|
||||
throw;
|
||||
}
|
||||
Mount(local_uri, std::move(db));
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
inline DatabasePtr
|
||||
|
||||
@@ -103,9 +103,11 @@ public:
|
||||
|
||||
/**
|
||||
* Throws #std::runtime_error on error.
|
||||
*
|
||||
* @return false if the mounted database needs to be updated
|
||||
*/
|
||||
gcc_nonnull_all
|
||||
void Mount(const char *local_uri, const char *storage_uri);
|
||||
bool Mount(const char *local_uri, const char *storage_uri);
|
||||
|
||||
gcc_nonnull_all
|
||||
bool Unmount(const char *uri) noexcept;
|
||||
|
||||
@@ -89,9 +89,18 @@ public:
|
||||
tag.Clear();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsRoot() const noexcept {
|
||||
return type == Type::CONTAINER && id == "0";
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool Check() const noexcept {
|
||||
return !id.empty() && !parent_id.empty() && !name.empty() &&
|
||||
return !id.empty() &&
|
||||
/* root nodes don't need a parent id and a
|
||||
name */
|
||||
(IsRoot() || (!parent_id.empty() &&
|
||||
!name.empty())) &&
|
||||
(type != UPnPDirObject::Type::ITEM ||
|
||||
item_class != UPnPDirObject::ItemClass::UNKNOWN);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileInfo.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <string>
|
||||
@@ -146,8 +147,7 @@ WatchDirectory::GetUriFS() const noexcept
|
||||
/* we don't look at "." / ".." nor files with newlines in their name */
|
||||
static bool skip_path(const char *path)
|
||||
{
|
||||
return (path[0] == '.' && path[1] == 0) ||
|
||||
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
|
||||
return PathTraitsFS::IsSpecialFilename(path) ||
|
||||
strchr(path, '\n') != nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
|
||||
~UpdateService();
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return defer.GetEventLoop();
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ try {
|
||||
LogError(std::current_exception());
|
||||
}
|
||||
|
||||
/* we don't look at "." / ".." nor files with newlines in their name */
|
||||
/* we don't look at files with newlines in their name */
|
||||
gcc_pure
|
||||
static bool
|
||||
skip_path(const char *name_utf8) noexcept
|
||||
@@ -341,8 +341,8 @@ UpdateWalk::UpdateDirectory(Directory &directory,
|
||||
|
||||
try {
|
||||
Mutex mutex;
|
||||
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
|
||||
".mpdignore").c_str(),
|
||||
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
|
||||
".mpdignore").c_str()).c_str(),
|
||||
mutex);
|
||||
child_exclude_list.Load(std::move(is));
|
||||
} catch (...) {
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
@@ -344,6 +346,10 @@ DecoderBridge::SeekError()
|
||||
/* d'oh, we can't seek to the sub-song start position,
|
||||
what now? - no idea, ignoring the problem for now. */
|
||||
initial_seek_running = false;
|
||||
|
||||
if (initial_seek_essential)
|
||||
error = std::make_exception_ptr(std::runtime_error("Decoder failed to seek"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ public:
|
||||
*/
|
||||
bool initial_seek_pending;
|
||||
|
||||
/**
|
||||
* Are initial seek failures fatal?
|
||||
*/
|
||||
const bool initial_seek_essential;
|
||||
|
||||
/**
|
||||
* Is the initial seek currently running? During this time,
|
||||
* the decoder command is SEEK. This flag is set by
|
||||
@@ -107,9 +112,11 @@ public:
|
||||
std::exception_ptr error;
|
||||
|
||||
DecoderBridge(DecoderControl &_dc, bool _initial_seek_pending,
|
||||
bool _initial_seek_essential,
|
||||
std::unique_ptr<Tag> _tag)
|
||||
:dc(_dc),
|
||||
initial_seek_pending(_initial_seek_pending),
|
||||
initial_seek_essential(_initial_seek_essential),
|
||||
song_tag(std::move(_tag)) {}
|
||||
|
||||
~DecoderBridge();
|
||||
|
||||
@@ -90,6 +90,7 @@ DecoderControl::IsCurrentSong(const DetachedSong &_song) const noexcept
|
||||
void
|
||||
DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
|
||||
SongTime _start_time, SongTime _end_time,
|
||||
bool _initial_seek_essential,
|
||||
MusicBuffer &_buffer,
|
||||
std::shared_ptr<MusicPipe> _pipe) noexcept
|
||||
{
|
||||
@@ -99,6 +100,7 @@ DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
|
||||
song = std::move(_song);
|
||||
start_time = _start_time;
|
||||
end_time = _end_time;
|
||||
initial_seek_essential = _initial_seek_essential;
|
||||
buffer = &_buffer;
|
||||
pipe = std::move(_pipe);
|
||||
|
||||
|
||||
@@ -117,6 +117,12 @@ public:
|
||||
|
||||
bool seek_error;
|
||||
bool seekable;
|
||||
|
||||
/**
|
||||
* @see #DecoderBridge::initial_seek_essential
|
||||
*/
|
||||
bool initial_seek_essential;
|
||||
|
||||
SongTime seek_time;
|
||||
|
||||
private:
|
||||
@@ -398,11 +404,14 @@ public:
|
||||
* owned and freed by the decoder
|
||||
* @param start_time see #DecoderControl
|
||||
* @param end_time see #DecoderControl
|
||||
* @param initial_seek_essential see
|
||||
* #DecoderBridge::initial_seek_essential
|
||||
* @param pipe the pipe which receives the decoded chunks (owned by
|
||||
* the caller)
|
||||
*/
|
||||
void Start(std::unique_ptr<DetachedSong> song,
|
||||
SongTime start_time, SongTime end_time,
|
||||
bool initial_seek_essential,
|
||||
MusicBuffer &buffer,
|
||||
std::shared_ptr<MusicPipe> pipe) noexcept;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <forward_list>
|
||||
#include <forward_list> // IWYU pragma: export
|
||||
|
||||
struct ConfigBlock;
|
||||
class InputStream;
|
||||
@@ -67,18 +67,22 @@ struct DecoderPlugin {
|
||||
void (*file_decode)(DecoderClient &client, Path path_fs);
|
||||
|
||||
/**
|
||||
* Scan metadata of a file.
|
||||
* Scan metadata of a file.
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return false if the operation has failed
|
||||
* @return false if the file was not recognized
|
||||
*/
|
||||
bool (*scan_file)(Path path_fs, TagHandler &handler) noexcept;
|
||||
bool (*scan_file)(Path path_fs, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Scan metadata of a file.
|
||||
* Scan metadata of a stream.
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return false if the operation has failed
|
||||
* @return false if the stream was not recognized
|
||||
*/
|
||||
bool (*scan_stream)(InputStream &is, TagHandler &handler) noexcept;
|
||||
bool (*scan_stream)(InputStream &is, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* @brief Return a "virtual" filename for subtracks in
|
||||
@@ -135,7 +139,7 @@ struct DecoderPlugin {
|
||||
* Read the tag of a file.
|
||||
*/
|
||||
template<typename P>
|
||||
bool ScanFile(P path_fs, TagHandler &handler) const noexcept {
|
||||
bool ScanFile(P path_fs, TagHandler &handler) const {
|
||||
return scan_file != nullptr
|
||||
? scan_file(path_fs, handler)
|
||||
: false;
|
||||
@@ -144,7 +148,7 @@ struct DecoderPlugin {
|
||||
/**
|
||||
* Read the tag of a stream.
|
||||
*/
|
||||
bool ScanStream(InputStream &is, TagHandler &handler) const noexcept {
|
||||
bool ScanStream(InputStream &is, TagHandler &handler) const {
|
||||
return scan_stream != nullptr
|
||||
? scan_stream(is, handler)
|
||||
: false;
|
||||
|
||||
@@ -461,6 +461,7 @@ decoder_run_song(DecoderControl &dc,
|
||||
dc.start_time = dc.seek_time;
|
||||
|
||||
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
|
||||
dc.initial_seek_essential,
|
||||
/* pass the song tag only if it's
|
||||
authoritative, i.e. if it's a local
|
||||
file - tags on "stream" songs are just
|
||||
|
||||
@@ -41,7 +41,7 @@ adplug_init(const ConfigBlock &block)
|
||||
FormatDebug(adplug_domain, "adplug %s",
|
||||
CAdPlug::get_version().c_str());
|
||||
|
||||
sample_rate = block.GetPositiveValue("sample_rate", 48000u);
|
||||
sample_rate = block.GetPositiveValue("sample_rate", 48000U);
|
||||
CheckSampleRate(sample_rate);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -241,7 +241,7 @@ audiofile_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
audiofile_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
if (!is.IsSeekable() || !is.KnownSize())
|
||||
return false;
|
||||
@@ -269,6 +269,8 @@ static const char *const audiofile_suffixes[] = {
|
||||
};
|
||||
|
||||
static const char *const audiofile_mime_types[] = {
|
||||
"audio/wav",
|
||||
"audio/aiff",
|
||||
"audio/x-wav",
|
||||
"audio/x-aiff",
|
||||
nullptr
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_DECODER_DSDLIB_HXX
|
||||
#define MPD_DECODER_DSDLIB_HXX
|
||||
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "input/Offset.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/bit_reverse.h"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "DsdLib.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -449,7 +449,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
dsdiff_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
DsdiffMetaData metadata;
|
||||
DsdiffChunkHeader chunk_header;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/bit_reverse.h"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "DsdLib.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -326,7 +326,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
dsf_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
/* check DSF metadata */
|
||||
DsfMetaData metadata;
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Math.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <neaacdec.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <exception>
|
||||
|
||||
#include <assert.h>
|
||||
@@ -414,7 +414,7 @@ faad_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
faad_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
faad_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
auto result = faad_get_file_time(is);
|
||||
if (!result.first)
|
||||
|
||||
@@ -297,7 +297,7 @@ FfmpegReceiveFrames(DecoderClient &client, InputStream &is,
|
||||
*/
|
||||
static DecoderCommand
|
||||
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
|
||||
AVPacket &&packet,
|
||||
const AVPacket &packet,
|
||||
AVCodecContext &codec_context,
|
||||
const AVStream &stream,
|
||||
AVFrame &frame,
|
||||
@@ -350,24 +350,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is,
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static DecoderCommand
|
||||
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
|
||||
const AVPacket &packet,
|
||||
AVCodecContext &codec_context,
|
||||
const AVStream &stream,
|
||||
AVFrame &frame,
|
||||
uint64_t min_frame, size_t pcm_frame_size,
|
||||
FfmpegBuffer &buffer)
|
||||
{
|
||||
return ffmpeg_send_packet(client, is,
|
||||
/* copy the AVPacket, because FFmpeg
|
||||
< 3.0 requires this */
|
||||
AVPacket(packet),
|
||||
codec_context, stream,
|
||||
frame, min_frame, pcm_frame_size,
|
||||
buffer);
|
||||
}
|
||||
|
||||
gcc_const
|
||||
static SampleFormat
|
||||
ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept
|
||||
@@ -664,8 +646,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
|
||||
}
|
||||
|
||||
static bool
|
||||
FfmpegScanStream(AVFormatContext &format_context,
|
||||
TagHandler &handler) noexcept
|
||||
FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
||||
{
|
||||
const int find_result =
|
||||
avformat_find_stream_info(&format_context, nullptr);
|
||||
@@ -698,7 +679,7 @@ FfmpegScanStream(AVFormatContext &format_context,
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
AvioStream stream(nullptr, is);
|
||||
if (!stream.Open())
|
||||
@@ -762,7 +743,7 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"audio/aac",
|
||||
"audio/aacp",
|
||||
"audio/ac3",
|
||||
"audio/aiff"
|
||||
"audio/aiff",
|
||||
"audio/amr",
|
||||
"audio/basic",
|
||||
"audio/flac",
|
||||
@@ -775,12 +756,13 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"audio/qcelp",
|
||||
"audio/vorbis",
|
||||
"audio/vorbis+ogg",
|
||||
"audio/wav",
|
||||
"audio/x-8svx",
|
||||
"audio/x-16sv",
|
||||
"audio/x-aac",
|
||||
"audio/x-ac3",
|
||||
"audio/x-adx",
|
||||
"audio/x-aiff"
|
||||
"audio/x-aiff",
|
||||
"audio/x-alaw",
|
||||
"audio/x-au",
|
||||
"audio/x-dca",
|
||||
@@ -800,7 +782,7 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"audio/x-pn-realaudio",
|
||||
"audio/x-pn-multirate-realaudio",
|
||||
"audio/x-speex",
|
||||
"audio/x-tta"
|
||||
"audio/x-tta",
|
||||
"audio/x-voc",
|
||||
"audio/x-wav",
|
||||
"audio/x-wma",
|
||||
|
||||
@@ -69,7 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
flac_scan_file(Path path_fs, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.Read(NarrowPath(path_fs))) {
|
||||
@@ -84,7 +84,7 @@ flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
flac_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.Read(is)) {
|
||||
@@ -313,7 +313,7 @@ oggflac_init(gcc_unused const ConfigBlock &block)
|
||||
}
|
||||
|
||||
static bool
|
||||
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
oggflac_scan_file(Path path_fs, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.ReadOgg(NarrowPath(path_fs))) {
|
||||
@@ -328,7 +328,7 @@ oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
oggflac_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.ReadOgg(is)) {
|
||||
|
||||
@@ -70,15 +70,15 @@ fluidsynth_mpd_log_function(int level,
|
||||
char *message,
|
||||
void *)
|
||||
{
|
||||
Log(fluidsynth_domain,
|
||||
fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||
Log(fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||
fluidsynth_domain,
|
||||
message);
|
||||
}
|
||||
|
||||
static bool
|
||||
fluidsynth_init(const ConfigBlock &block)
|
||||
{
|
||||
sample_rate = block.GetPositiveValue("sample_rate", 48000u);
|
||||
sample_rate = block.GetPositiveValue("sample_rate", 48000U);
|
||||
CheckSampleRate(sample_rate);
|
||||
|
||||
soundfont_path = block.GetBlockValue("soundfont",
|
||||
|
||||
@@ -27,9 +27,10 @@
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/NarrowPath.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -37,7 +38,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
@@ -75,11 +75,10 @@ gcc_pure
|
||||
static unsigned
|
||||
ParseSubtuneName(const char *base) noexcept
|
||||
{
|
||||
if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
|
||||
base = StringAfterPrefix(base, SUBTUNE_PREFIX);
|
||||
if (base == nullptr)
|
||||
return 0;
|
||||
|
||||
base += sizeof(SUBTUNE_PREFIX) - 1;
|
||||
|
||||
char *endptr;
|
||||
auto track = strtoul(base, &endptr, 10);
|
||||
if (endptr == base || *endptr != '.')
|
||||
@@ -98,41 +97,46 @@ ParseContainerPath(Path path_fs)
|
||||
const Path base = path_fs.GetBase();
|
||||
unsigned track;
|
||||
if (base.IsNull() ||
|
||||
(track = ParseSubtuneName(base.c_str())) < 1)
|
||||
(track = ParseSubtuneName(NarrowPath(base))) < 1)
|
||||
return { AllocatedPath(path_fs), 0 };
|
||||
|
||||
return { path_fs.GetDirectoryName(), track - 1 };
|
||||
}
|
||||
|
||||
static AllocatedPath
|
||||
ReplaceSuffix(Path src,
|
||||
const PathTraitsFS::const_pointer_type new_suffix) noexcept
|
||||
{
|
||||
const auto *old_suffix = src.GetSuffix();
|
||||
if (old_suffix == nullptr)
|
||||
return nullptr;
|
||||
|
||||
PathTraitsFS::string s(src.c_str(), old_suffix);
|
||||
s += new_suffix;
|
||||
return AllocatedPath::FromFS(std::move(s));
|
||||
}
|
||||
|
||||
static Music_Emu*
|
||||
LoadGmeAndM3u(GmeContainerPath container) {
|
||||
|
||||
const char *path = container.path.c_str();
|
||||
const char *suffix = uri_get_suffix(path);
|
||||
|
||||
Music_Emu *emu;
|
||||
const char *gme_err =
|
||||
gme_open_file(path, &emu, GME_SAMPLE_RATE);
|
||||
gme_open_file(NarrowPath(container.path), &emu, GME_SAMPLE_RATE);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(suffix == nullptr) {
|
||||
return emu;
|
||||
}
|
||||
|
||||
std::string m3u_path(path,suffix);
|
||||
m3u_path += "m3u";
|
||||
|
||||
const auto m3u_path = ReplaceSuffix(container.path,
|
||||
PATH_LITERAL("m3u"));
|
||||
/*
|
||||
* Some GME formats lose metadata if you attempt to
|
||||
* load a non-existant M3U file, so check that one
|
||||
* exists before loading.
|
||||
*/
|
||||
if(FileExists(Path::FromFS(m3u_path.c_str()))) {
|
||||
gme_load_m3u(emu,m3u_path.c_str());
|
||||
}
|
||||
if (!m3u_path.IsNull() && FileExists(m3u_path))
|
||||
gme_load_m3u(emu, NarrowPath(m3u_path));
|
||||
|
||||
return emu;
|
||||
}
|
||||
|
||||
@@ -184,7 +188,11 @@ gme_file_decode(DecoderClient &client, Path path_fs)
|
||||
LogWarning(gme_domain, gme_err);
|
||||
|
||||
if (length > 0)
|
||||
gme_set_fade(emu, length);
|
||||
gme_set_fade(emu, length
|
||||
#if GME_VERSION >= 0x000700
|
||||
, 8000
|
||||
#endif
|
||||
);
|
||||
|
||||
/* play */
|
||||
DecoderCommand cmd;
|
||||
@@ -222,7 +230,7 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
|
||||
if (track_count > 1)
|
||||
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1));
|
||||
|
||||
if (info.song != nullptr) {
|
||||
if (!StringIsEmpty(info.song)) {
|
||||
if (track_count > 1) {
|
||||
/* start numbering subtunes from 1 */
|
||||
const auto tag_title =
|
||||
@@ -234,16 +242,16 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
|
||||
handler.OnTag(TAG_TITLE, info.song);
|
||||
}
|
||||
|
||||
if (info.author != nullptr)
|
||||
if (!StringIsEmpty(info.author))
|
||||
handler.OnTag(TAG_ARTIST, info.author);
|
||||
|
||||
if (info.game != nullptr)
|
||||
if (!StringIsEmpty(info.game))
|
||||
handler.OnTag(TAG_ALBUM, info.game);
|
||||
|
||||
if (info.comment != nullptr)
|
||||
if (!StringIsEmpty(info.comment))
|
||||
handler.OnTag(TAG_COMMENT, info.comment);
|
||||
|
||||
if (info.copyright != nullptr)
|
||||
if (!StringIsEmpty(info.copyright))
|
||||
handler.OnTag(TAG_DATE, info.copyright);
|
||||
}
|
||||
|
||||
@@ -298,7 +306,7 @@ gme_container_scan(Path path_fs)
|
||||
if (num_songs < 2)
|
||||
return list;
|
||||
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
|
||||
const auto *subtune_suffix = path_fs.GetSuffix();
|
||||
|
||||
TagBuilder tag_builder;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "HybridDsdDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
#include "util/StaticFifoBuffer.hxx"
|
||||
@@ -186,7 +186,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input)
|
||||
client.Ready(result.first, true, duration);
|
||||
frame_size = result.first.GetFrameSize();
|
||||
kbit_rate = frame_size * result.first.sample_rate /
|
||||
(1024u / 8u);
|
||||
(1024U / 8U);
|
||||
total_frames = result.second / frame_size;
|
||||
} catch (UnsupportedFile) {
|
||||
/* not a Hybrid-DSD file; let the next decoder plugin
|
||||
@@ -236,7 +236,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input)
|
||||
/* fill the buffer */
|
||||
auto w = buffer.Write();
|
||||
if (!w.empty()) {
|
||||
if (remaining_bytes < (1<<30ull) &&
|
||||
if (remaining_bytes < (1<<30ULL) &&
|
||||
w.size > size_t(remaining_bytes))
|
||||
w.size = remaining_bytes;
|
||||
|
||||
|
||||
@@ -793,7 +793,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
|
||||
|
||||
if (max_frames > 8 * 1024 * 1024) {
|
||||
FormatWarning(mad_domain,
|
||||
"mp3 file header indicates too many frames: %lu",
|
||||
"mp3 file header indicates too many frames: %zu",
|
||||
max_frames);
|
||||
return false;
|
||||
}
|
||||
@@ -1051,7 +1051,7 @@ MadDecoder::RunScan(TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
MadDecoder data(nullptr, is);
|
||||
return data.RunScan(handler);
|
||||
|
||||
@@ -107,7 +107,7 @@ mikmod_decoder_init(const ConfigBlock &block)
|
||||
static char params[] = "";
|
||||
|
||||
mikmod_loop = block.GetBlockValue("loop", false);
|
||||
mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100u);
|
||||
mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100U);
|
||||
if (!audio_valid_sample_rate(mikmod_sample_rate))
|
||||
throw FormatRuntimeError("Invalid sample rate in line %d: %u",
|
||||
block.line, mikmod_sample_rate);
|
||||
|
||||
@@ -26,8 +26,13 @@
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <libmodplug/modplug.h>
|
||||
#ifdef _WIN32
|
||||
/* assume ModPlug is built as static library on Windows; without
|
||||
this, linking to the static library would fail */
|
||||
#define MODPLUG_STATIC
|
||||
#endif
|
||||
|
||||
#include <libmodplug/modplug.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ mpcdec_get_file_duration(InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
mpcdec_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
mpcdec_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
const auto duration = mpcdec_get_file_duration(is);
|
||||
if (duration.IsNegative())
|
||||
|
||||
@@ -47,8 +47,12 @@ OggDecoder::LoadEndPacket(ogg_packet &packet) const
|
||||
DecoderReader reader(client, input_stream);
|
||||
OggSyncState sync2(reader);
|
||||
OggStreamState stream2(GetSerialNo());
|
||||
|
||||
/* passing synced=false because we're inside an
|
||||
OggVisitor callback, and our InputStream may be in
|
||||
the middle of an Ogg packet */
|
||||
result = OggSeekFindEOS(sync2, stream2, packet,
|
||||
input_stream);
|
||||
input_stream, false);
|
||||
}
|
||||
|
||||
/* restore the previous file position */
|
||||
|
||||
@@ -75,6 +75,19 @@ class MPDOpusDecoder final : public OggDecoder {
|
||||
OpusDecoder *opus_decoder = nullptr;
|
||||
opus_int16 *output_buffer = nullptr;
|
||||
|
||||
/**
|
||||
* The pre-skip value from the Opus header. Initialized by
|
||||
* OnOggBeginning().
|
||||
*/
|
||||
unsigned pre_skip;
|
||||
|
||||
/**
|
||||
* The number of decoded samples which shall be skipped. At
|
||||
* the beginning of the file, this gets set to #pre_skip (by
|
||||
* OnOggBeginning()), and may also be set while seeking.
|
||||
*/
|
||||
unsigned skip;
|
||||
|
||||
/**
|
||||
* If non-zero, then a previous Opus stream has been found
|
||||
* already with this number of channels. If opus_decoder is
|
||||
@@ -85,6 +98,13 @@ class MPDOpusDecoder final : public OggDecoder {
|
||||
|
||||
size_t frame_size;
|
||||
|
||||
/**
|
||||
* The granulepos of the next sample to be submitted to
|
||||
* DecoderClient::SubmitData(). Negative if unkown.
|
||||
* Initialized by OnOggBeginning().
|
||||
*/
|
||||
ogg_int64_t granulepos;
|
||||
|
||||
public:
|
||||
explicit MPDOpusDecoder(DecoderReader &reader)
|
||||
:OggDecoder(reader) {}
|
||||
@@ -101,6 +121,13 @@ public:
|
||||
bool Seek(uint64_t where_frame);
|
||||
|
||||
private:
|
||||
void AddGranulepos(ogg_int64_t n) noexcept {
|
||||
assert(n >= 0);
|
||||
|
||||
if (granulepos >= 0)
|
||||
granulepos += n;
|
||||
}
|
||||
|
||||
void HandleTags(const ogg_packet &packet);
|
||||
void HandleAudio(const ogg_packet &packet);
|
||||
|
||||
@@ -137,10 +164,13 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
|
||||
throw std::runtime_error("BOS packet must be OpusHead");
|
||||
|
||||
unsigned channels;
|
||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels, pre_skip) ||
|
||||
!audio_valid_channel_count(channels))
|
||||
throw std::runtime_error("Malformed BOS packet");
|
||||
|
||||
granulepos = 0;
|
||||
skip = pre_skip;
|
||||
|
||||
assert(opus_decoder == nullptr);
|
||||
assert(IsInitialized() == (output_buffer != nullptr));
|
||||
|
||||
@@ -177,8 +207,12 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
|
||||
client.Ready(audio_format, eos_granulepos > 0, duration);
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
|
||||
output_buffer = new opus_int16[opus_output_buffer_frames
|
||||
* audio_format.channels];
|
||||
if (output_buffer == nullptr)
|
||||
/* note: if we ever support changing the channel count
|
||||
in chained streams, we need to reallocate this
|
||||
buffer instead of keeping it */
|
||||
output_buffer = new opus_int16[opus_output_buffer_frames
|
||||
* audio_format.channels];
|
||||
|
||||
auto cmd = client.GetCommand();
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
@@ -231,22 +265,58 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
||||
packet.bytes,
|
||||
output_buffer, opus_output_buffer_frames,
|
||||
0);
|
||||
if (nframes < 0)
|
||||
throw FormatRuntimeError("libopus error: %s",
|
||||
opus_strerror(nframes));
|
||||
|
||||
if (nframes > 0) {
|
||||
const size_t nbytes = nframes * frame_size;
|
||||
auto cmd = client.SubmitData(input_stream,
|
||||
output_buffer, nbytes,
|
||||
0);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
throw cmd;
|
||||
|
||||
if (packet.granulepos > 0)
|
||||
client.SubmitTimestamp(FloatDuration(packet.granulepos)
|
||||
/ opus_sample_rate);
|
||||
if (gcc_unlikely(nframes <= 0)) {
|
||||
if (nframes < 0)
|
||||
throw FormatRuntimeError("libopus error: %s",
|
||||
opus_strerror(nframes));
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
/* apply the "skip" value */
|
||||
if (skip >= (unsigned)nframes) {
|
||||
skip -= nframes;
|
||||
AddGranulepos(nframes);
|
||||
return;
|
||||
}
|
||||
|
||||
const opus_int16 *data = output_buffer;
|
||||
data += skip * previous_channels;
|
||||
nframes -= skip;
|
||||
AddGranulepos(skip);
|
||||
skip = 0;
|
||||
|
||||
if (packet.e_o_s && packet.granulepos > 0 && granulepos >= 0) {
|
||||
/* End Trimming (RFC7845 4.4): "The page with the 'end
|
||||
of stream' flag set MAY have a granule position
|
||||
that indicates the page contains less audio data
|
||||
than would normally be returned by decoding up
|
||||
through the final packet. This is used to end the
|
||||
stream somewhere other than an even frame
|
||||
boundary. [...] The remaining samples are
|
||||
discarded. */
|
||||
ogg_int64_t remaining = packet.granulepos - granulepos;
|
||||
if (remaining <= 0)
|
||||
return;
|
||||
|
||||
if (remaining < nframes)
|
||||
nframes = remaining;
|
||||
}
|
||||
|
||||
/* submit decoded samples to the DecoderClient */
|
||||
const size_t nbytes = nframes * frame_size;
|
||||
auto cmd = client.SubmitData(input_stream,
|
||||
data, nbytes,
|
||||
0);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
throw cmd;
|
||||
|
||||
if (packet.granulepos > 0) {
|
||||
granulepos = packet.granulepos;
|
||||
client.SubmitTimestamp(FloatDuration(granulepos - pre_skip)
|
||||
/ opus_sample_rate);
|
||||
} else
|
||||
AddGranulepos(nframes);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -258,8 +328,20 @@ MPDOpusDecoder::Seek(uint64_t where_frame)
|
||||
|
||||
const ogg_int64_t where_granulepos(where_frame);
|
||||
|
||||
/* we don't know the exact granulepos after seeking, so let's
|
||||
set it to -1 - it will be set after the next packet which
|
||||
declares its granulepos */
|
||||
granulepos = -1;
|
||||
|
||||
try {
|
||||
SeekGranulePos(where_granulepos);
|
||||
|
||||
/* since all frame numbers are offset by the file's
|
||||
pre-skip value, we need to apply it here as well;
|
||||
we could just seek to "where_frame+pre_skip" as
|
||||
well, but I think by decoding those samples and
|
||||
discard them, we're safer */
|
||||
skip = pre_skip;
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
@@ -302,13 +384,14 @@ mpd_opus_stream_decode(DecoderClient &client,
|
||||
|
||||
static bool
|
||||
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
|
||||
unsigned &channels)
|
||||
unsigned &channels, unsigned &pre_skip)
|
||||
{
|
||||
ogg_packet packet;
|
||||
|
||||
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
|
||||
IsOpusHead(packet) &&
|
||||
ScanOpusHeader(packet.packet, packet.bytes, channels) &&
|
||||
ScanOpusHeader(packet.packet, packet.bytes, channels,
|
||||
pre_skip) &&
|
||||
audio_valid_channel_count(channels);
|
||||
}
|
||||
|
||||
@@ -327,11 +410,12 @@ ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream,
|
||||
|
||||
static void
|
||||
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
||||
TagHandler &handler)
|
||||
ogg_int64_t pre_skip, TagHandler &handler)
|
||||
{
|
||||
ogg_packet packet;
|
||||
|
||||
if (OggSeekFindEOS(sync, stream, packet, is)) {
|
||||
if (OggSeekFindEOS(sync, stream, packet, is) &&
|
||||
packet.granulepos >= pre_skip) {
|
||||
const auto duration =
|
||||
SongTime::FromScale<uint64_t>(packet.granulepos,
|
||||
opus_sample_rate);
|
||||
@@ -340,7 +424,7 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
||||
}
|
||||
|
||||
static bool
|
||||
mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
InputStreamReader reader(is);
|
||||
OggSyncState oy(reader);
|
||||
@@ -351,15 +435,15 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
|
||||
OggStreamState os(first_page);
|
||||
|
||||
unsigned channels;
|
||||
if (!ReadAndParseOpusHead(oy, os, channels) ||
|
||||
unsigned channels, pre_skip;
|
||||
if (!ReadAndParseOpusHead(oy, os, channels, pre_skip) ||
|
||||
!ReadAndVisitOpusTags(oy, os, handler))
|
||||
return false;
|
||||
|
||||
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
|
||||
SampleFormat::S16, channels));
|
||||
|
||||
VisitOpusDuration(is, oy, os, handler);
|
||||
VisitOpusDuration(is, oy, os, pre_skip, handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "OpusHead.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -31,12 +32,14 @@ struct OpusHead {
|
||||
};
|
||||
|
||||
bool
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
|
||||
unsigned &pre_skip_r)
|
||||
{
|
||||
const OpusHead *h = (const OpusHead *)data;
|
||||
if (size < 19 || (h->version & 0xf0) != 0)
|
||||
return false;
|
||||
|
||||
channels_r = h->channels;
|
||||
pre_skip_r = FromLE16(h->pre_skip);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <stddef.h>
|
||||
|
||||
bool
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
|
||||
unsigned &pre_skip_r);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "pcm/PcmPack.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ByteReverse.hxx"
|
||||
#include "util/StaticFifoBuffer.hxx"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
@@ -115,7 +115,7 @@ sidplay_init(const ConfigBlock &block)
|
||||
if (!database_path.IsNull())
|
||||
songlength_database = sidplay_load_songlength_db(database_path);
|
||||
|
||||
default_songlength = block.GetPositiveValue("default_songlength", 0u);
|
||||
default_songlength = block.GetPositiveValue("default_songlength", 0U);
|
||||
|
||||
all_files_are_containers =
|
||||
block.GetBlockValue("all_files_are_containers", true);
|
||||
@@ -387,7 +387,7 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
||||
const unsigned timebase = player.timebase();
|
||||
#endif
|
||||
const unsigned end = duration.IsNegative()
|
||||
? 0u
|
||||
? 0U
|
||||
: duration.ToScale<uint64_t>(timebase);
|
||||
|
||||
DecoderCommand cmd;
|
||||
|
||||
@@ -267,7 +267,7 @@ static constexpr struct {
|
||||
};
|
||||
|
||||
static bool
|
||||
sndfile_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
sndfile_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
SF_INFO info;
|
||||
|
||||
@@ -322,6 +322,8 @@ static const char *const sndfile_suffixes[] = {
|
||||
};
|
||||
|
||||
static const char *const sndfile_mime_types[] = {
|
||||
"audio/wav",
|
||||
"audio/aiff",
|
||||
"audio/x-wav",
|
||||
"audio/x-aiff",
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ VisitVorbisDuration(InputStream &is,
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
vorbis_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
/* initialize libogg */
|
||||
|
||||
|
||||
@@ -614,7 +614,7 @@ wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
wavpack_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
wavpack_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
WavpackInput isp(nullptr, is);
|
||||
|
||||
|
||||
@@ -25,9 +25,16 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/NarrowPath.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "PluginUnavailable.hxx"
|
||||
|
||||
#ifdef _WIN32
|
||||
/* assume WildMidi is built as static library on Windows; without
|
||||
this, linking to the static library would fail */
|
||||
#define WILDMIDI_STATIC
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include <wildmidi_lib.h>
|
||||
}
|
||||
@@ -53,7 +60,8 @@ wildmidi_init(const ConfigBlock &block)
|
||||
AtScopeExit() { WildMidi_ClearError(); };
|
||||
#endif
|
||||
|
||||
if (WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
|
||||
if (WildMidi_Init(NarrowPath(path),
|
||||
wildmidi_audio_format.sample_rate,
|
||||
0) != 0) {
|
||||
#ifdef LIBWILDMIDI_VERSION
|
||||
/* WildMidi_GetError() requires libwildmidi 0.4 */
|
||||
@@ -96,7 +104,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
|
||||
midi *wm;
|
||||
const struct _WM_Info *info;
|
||||
|
||||
wm = WildMidi_Open(path_fs.c_str());
|
||||
wm = WildMidi_Open(NarrowPath(path_fs));
|
||||
if (wm == nullptr)
|
||||
return;
|
||||
|
||||
@@ -136,7 +144,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
|
||||
static bool
|
||||
wildmidi_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
{
|
||||
midi *wm = WildMidi_Open(path_fs.c_str());
|
||||
midi *wm = WildMidi_Open(NarrowPath(path_fs));
|
||||
if (wm == nullptr)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -129,7 +129,16 @@ if wavpack_dep.found()
|
||||
decoder_plugins_sources += 'WavpackDecoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
wildmidi_dep = c_compiler.find_library('WildMidi', required: get_option('wildmidi'))
|
||||
wildmidi_required = get_option('wildmidi')
|
||||
if wildmidi_required.enabled()
|
||||
# if the user has force-enabled WildMidi, allow the pkg-config test
|
||||
# to fail; after that, the find_library() check must succeed
|
||||
wildmidi_required = false
|
||||
endif
|
||||
wildmidi_dep = dependency('wildmidi', required: wildmidi_required)
|
||||
if not wildmidi_dep.found()
|
||||
wildmidi_dep = c_compiler.find_library('WildMidi', required: get_option('wildmidi'))
|
||||
endif
|
||||
conf.set('ENABLE_WILDMIDI', wildmidi_dep.found())
|
||||
if wildmidi_dep.found()
|
||||
decoder_plugins_sources += 'WildmidiDecoderPlugin.cxx'
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
};
|
||||
|
||||
PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block)
|
||||
:compression(block.GetBlockValue("compression", 5u))
|
||||
:compression(block.GetBlockValue("compression", 5U))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "AudioFormat.hxx"
|
||||
#include "config/Domain.hxx"
|
||||
#include "util/Alloc.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
|
||||
#include <opus.h>
|
||||
@@ -107,7 +107,7 @@ PreparedOpusEncoder::PreparedOpusEncoder(const ConfigBlock &block)
|
||||
throw std::runtime_error("Invalid bit rate");
|
||||
}
|
||||
|
||||
complexity = block.GetBlockValue("complexity", 10u);
|
||||
complexity = block.GetBlockValue("complexity", 10U);
|
||||
if (complexity > 10)
|
||||
throw std::runtime_error("Invalid complexity");
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "WaveEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
Cancel();
|
||||
}
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
EventLoop &GetEventLoop() const noexcept {
|
||||
return loop;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
:defer(_loop, BIND_THIS_METHOD(RunDeferred)),
|
||||
callback(_callback), pending_mask(0) {}
|
||||
|
||||
EventLoop &GetEventLoop() {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return defer.GetEventLoop();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
#include "PollGroupWinSelect.hxx"
|
||||
|
||||
constexpr int EVENT_READ = 0;
|
||||
constexpr int EVENT_WRITE = 1;
|
||||
static constexpr int EVENT_READ = 0;
|
||||
static constexpr int EVENT_WRITE = 1;
|
||||
|
||||
static constexpr
|
||||
bool HasEvent(unsigned events, int event_id) noexcept
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
#include "SocketMonitor.hxx"
|
||||
#include "Loop.hxx"
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -86,6 +90,21 @@ SocketMonitor::Schedule(unsigned flags) noexcept
|
||||
|
||||
if (success)
|
||||
scheduled_flags = flags;
|
||||
#ifdef USE_EPOLL
|
||||
else if (errno == EBADF || errno == ENOENT)
|
||||
/* the socket was probably closed by somebody else
|
||||
(EBADF) or a new file descriptor with the same
|
||||
number was created but not registered already
|
||||
(ENOENT) - we can assume that there are no
|
||||
scheduled events */
|
||||
/* note that when this happens, we're actually lucky
|
||||
that it has failed - imagine another thread may
|
||||
meanwhile have created something on the same file
|
||||
descriptor number, and has registered it; the
|
||||
epoll_ctl() call above would then have succeeded,
|
||||
but broke the other thread's epoll registration */
|
||||
scheduled_flags = 0;
|
||||
#endif
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
|
||||
~SocketMonitor() noexcept;
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return loop;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
}
|
||||
|
||||
bool ScheduleRead() noexcept {
|
||||
return Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
||||
return Schedule(GetScheduledFlags() | READ);
|
||||
}
|
||||
|
||||
bool ScheduleWrite() noexcept {
|
||||
@@ -117,7 +117,7 @@ public:
|
||||
}
|
||||
|
||||
void CancelRead() noexcept {
|
||||
Schedule(GetScheduledFlags() & ~(READ|HANGUP|ERROR));
|
||||
Schedule(GetScheduledFlags() & ~READ);
|
||||
}
|
||||
|
||||
void CancelWrite() noexcept {
|
||||
|
||||
@@ -62,7 +62,7 @@ public:
|
||||
Cancel();
|
||||
}
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return loop;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ event_dep = declare_dependency(
|
||||
link_with: event,
|
||||
dependencies: [
|
||||
thread_dep,
|
||||
net_dep,
|
||||
system_dep,
|
||||
boost_dep,
|
||||
],
|
||||
|
||||
@@ -285,6 +285,11 @@ public:
|
||||
bool IsAbsolute() const noexcept {
|
||||
return Traits::IsAbsolute(c_str());
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
const_pointer_type GetSuffix() const noexcept {
|
||||
return ((Path)*this).GetSuffix();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
55
src/fs/NarrowPath.cxx
Normal file
55
src/fs/NarrowPath.cxx
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "NarrowPath.hxx"
|
||||
|
||||
#ifdef _UNICODE
|
||||
|
||||
#include "lib/icu/Win32.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
NarrowPath::NarrowPath(Path _path) noexcept
|
||||
:value(WideCharToMultiByte(CP_ACP, _path.c_str()))
|
||||
{
|
||||
if (value.IsNull())
|
||||
/* fall back to empty string */
|
||||
value = Value::Empty();
|
||||
}
|
||||
|
||||
static AllocatedPath
|
||||
AcpToAllocatedPath(const char *s)
|
||||
{
|
||||
wchar_t buffer[MAX_PATH];
|
||||
auto result = MultiByteToWideChar(CP_ACP, 0, s, -1,
|
||||
buffer, ARRAY_SIZE(buffer));
|
||||
if (result <= 0)
|
||||
throw MakeLastError("MultiByteToWideChar() failed");
|
||||
|
||||
return AllocatedPath::FromFS(buffer);
|
||||
}
|
||||
|
||||
FromNarrowPath::FromNarrowPath(const char *s)
|
||||
:value(AcpToAllocatedPath(s))
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* _UNICODE */
|
||||
@@ -21,12 +21,10 @@
|
||||
#define MPD_FS_NARROW_PATH_HXX
|
||||
|
||||
#include "Path.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
|
||||
#ifdef _UNICODE
|
||||
#include "lib/icu/Win32.hxx"
|
||||
#include "AllocatedPath.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include "util/StringPointer.hxx"
|
||||
#endif
|
||||
@@ -48,12 +46,7 @@ class NarrowPath {
|
||||
|
||||
public:
|
||||
#ifdef _UNICODE
|
||||
explicit NarrowPath(Path _path)
|
||||
:value(WideCharToMultiByte(CP_ACP, _path.c_str())) {
|
||||
if (value.IsNull())
|
||||
/* fall back to empty string */
|
||||
value = Value::Empty();
|
||||
}
|
||||
explicit NarrowPath(Path _path) noexcept;
|
||||
#else
|
||||
explicit NarrowPath(Path _path):value(_path.c_str()) {}
|
||||
#endif
|
||||
@@ -67,4 +60,43 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A path name converted from a "narrow" string. This is used to
|
||||
* import an existing narrow string to a #Path.
|
||||
*/
|
||||
class FromNarrowPath {
|
||||
#ifdef _UNICODE
|
||||
using Value = AllocatedPath;
|
||||
#else
|
||||
using Value = Path;
|
||||
#endif
|
||||
|
||||
Value value{nullptr};
|
||||
|
||||
public:
|
||||
FromNarrowPath() = default;
|
||||
|
||||
#ifdef _UNICODE
|
||||
/**
|
||||
* Throws on error.
|
||||
*/
|
||||
FromNarrowPath(const char *s);
|
||||
#else
|
||||
constexpr FromNarrowPath(const char *s) noexcept
|
||||
:value(Value::FromFS(s)) {}
|
||||
#endif
|
||||
|
||||
#ifndef _UNICODE
|
||||
constexpr
|
||||
#endif
|
||||
operator Path() const noexcept {
|
||||
#ifdef _UNICODE
|
||||
if (value.IsNull())
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user