Compare commits
129 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 |
.gitignore.travis.ymlNEWS
android
doc
meson.buildpython/build
src
AudioFormat.hxxCommandLine.cxxMain.cxxStats.cxx
android
archive
plugins
command
db
plugins
update
decoder
encoder
event
DeferEvent.hxxMaskMonitor.hxxPollGroupWinSelect.cxxSocketMonitor.cxxSocketMonitor.hxxTimerEvent.hxxmeson.build
fs
input
java
lib
curl
dbus
ffmpeg
icu
nfs
pulse
upnp
mixer
neighbor
plugins
output
plugins
pcm
player
playlist
storage
tag
thread
time
util
zeroconf
test
ContainerScan.cxxDumpDatabase.cxxReadApeTags.cxxShutdownHandler.hxxWriteFile.cxxdump_playlist.cxxmeson.build
net
read_conf.cxxread_tags.cxxrun_decoder.cxxrun_filter.cxxrun_gunzip.cxxrun_input.cxxrun_output.cxxtime
win32
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@
|
||||
/output/
|
||||
|
||||
__pycache__/
|
||||
|
||||
/.clangd/
|
||||
/compile_commands.json
|
||||
|
20
.travis.yml
20
.travis.yml
@@ -1,6 +1,6 @@
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
jobs:
|
||||
include:
|
||||
# Ubuntu Bionic (18.04) with GCC 7
|
||||
- os: linux
|
||||
@@ -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"
|
||||
|
||||
|
66
NEWS
66
NEWS
@@ -1,3 +1,69 @@
|
||||
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"
|
||||
|
@@ -2,18 +2,25 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="43"
|
||||
android:versionName="0.21.20">
|
||||
android:versionCode="47"
|
||||
android:versionName="0.21.24">
|
||||
|
||||
<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.20'
|
||||
version = '0.21.24'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
@@ -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**
|
||||
|
19
meson.build
19
meson.build
@@ -1,11 +1,12 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.20',
|
||||
version: '0.21.24',
|
||||
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,6 +316,7 @@ subdir('src/util')
|
||||
subdir('src/time')
|
||||
subdir('src/system')
|
||||
subdir('src/thread')
|
||||
subdir('src/net')
|
||||
subdir('src/event')
|
||||
|
||||
subdir('src/lib/dbus')
|
||||
@@ -335,7 +341,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',
|
||||
)
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
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;
|
||||
};
|
||||
|
@@ -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);
|
||||
|
@@ -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 *
|
||||
|
@@ -449,12 +449,7 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||
|
||||
// TODO: update the new database instance?
|
||||
|
||||
try {
|
||||
Mount(local_uri, std::move(db));
|
||||
} catch (...) {
|
||||
db->Close();
|
||||
throw;
|
||||
}
|
||||
Mount(local_uri, std::move(db));
|
||||
}
|
||||
|
||||
inline DatabasePtr
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -78,7 +78,7 @@ fluidsynth_mpd_log_function(int level,
|
||||
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;
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -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");
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -108,6 +108,12 @@ struct PathTraitsFS {
|
||||
return IsSeparator(*p);
|
||||
}
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static bool IsSpecialFilename(const_pointer_type name) noexcept {
|
||||
return (name[0] == '.' && name[1] == 0) ||
|
||||
(name[0] == '.' && name[1] == '.' && name[2] == 0);
|
||||
}
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static size_t GetLength(const_pointer_type p) noexcept {
|
||||
return StringLength(p);
|
||||
@@ -216,6 +222,12 @@ struct PathTraitsUTF8 {
|
||||
return IsSeparator(*p);
|
||||
}
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static bool IsSpecialFilename(const_pointer_type name) noexcept {
|
||||
return (name[0] == '.' && name[1] == 0) ||
|
||||
(name[0] == '.' && name[1] == '.' && name[2] == 0);
|
||||
}
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static size_t GetLength(const_pointer_type p) noexcept {
|
||||
return StringLength(p);
|
||||
|
@@ -40,7 +40,7 @@ class BufferedReader {
|
||||
|
||||
public:
|
||||
explicit BufferedReader(Reader &_reader) noexcept
|
||||
:reader(_reader), buffer(4096) {}
|
||||
:reader(_reader), buffer(16384) {}
|
||||
|
||||
/**
|
||||
* Reset the internal state. Should be called after rewinding
|
||||
|
@@ -36,7 +36,7 @@ class GunzipReader final : public Reader {
|
||||
|
||||
z_stream z;
|
||||
|
||||
StaticFifoBuffer<Bytef, 4096> buffer;
|
||||
StaticFifoBuffer<Bytef, 65536> buffer;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
@@ -62,7 +62,7 @@ GzipOutputStream::Flush()
|
||||
z.avail_in = 0;
|
||||
|
||||
while (true) {
|
||||
Bytef output[4096];
|
||||
Bytef output[16384];
|
||||
z.next_out = output;
|
||||
z.avail_out = sizeof(output);
|
||||
|
||||
@@ -87,7 +87,7 @@ GzipOutputStream::Write(const void *_data, size_t size)
|
||||
z.avail_in = size;
|
||||
|
||||
while (z.avail_in > 0) {
|
||||
Bytef output[4096];
|
||||
Bytef output[16384];
|
||||
z.next_out = output;
|
||||
z.avail_out = sizeof(output);
|
||||
|
||||
|
@@ -6,6 +6,7 @@ fs_sources = [
|
||||
'Path.cxx',
|
||||
'Path2.cxx',
|
||||
'AllocatedPath.cxx',
|
||||
'NarrowPath.cxx',
|
||||
'FileSystem.cxx',
|
||||
'List.cxx',
|
||||
'StandardDirectory.cxx',
|
||||
|
@@ -76,7 +76,7 @@ public:
|
||||
|
||||
virtual ~AsyncInputStream();
|
||||
|
||||
EventLoop &GetEventLoop() {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return deferred_resume.GetEventLoop();
|
||||
}
|
||||
|
||||
|
@@ -113,7 +113,7 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||
throw FormatRuntimeError("Unrecognized 'default_byte_order' setting: %s",
|
||||
value);
|
||||
}
|
||||
speed = block.GetBlockValue("speed",0u);
|
||||
speed = block.GetBlockValue("speed",0U);
|
||||
}
|
||||
|
||||
struct CdioUri {
|
||||
|
@@ -320,7 +320,7 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block)
|
||||
http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
|
||||
|
||||
proxy = block.GetBlockValue("proxy");
|
||||
proxy_port = block.GetBlockValue("proxy_port", 0u);
|
||||
proxy_port = block.GetBlockValue("proxy_port", 0U);
|
||||
proxy_user = block.GetBlockValue("proxy_user");
|
||||
proxy_password = block.GetBlockValue("proxy_password");
|
||||
|
||||
@@ -365,9 +365,9 @@ CurlInputStream::InitEasy()
|
||||
request = new CurlRequest(**curl_init, GetURI(), *this);
|
||||
|
||||
request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
|
||||
request->SetOption(CURLOPT_FOLLOWLOCATION, 1l);
|
||||
request->SetOption(CURLOPT_MAXREDIRS, 5l);
|
||||
request->SetOption(CURLOPT_FAILONERROR, 1l);
|
||||
request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
|
||||
request->SetOption(CURLOPT_MAXREDIRS, 5L);
|
||||
request->SetOption(CURLOPT_FAILONERROR, 1L);
|
||||
|
||||
if (proxy != nullptr)
|
||||
request->SetOption(CURLOPT_PROXY, proxy);
|
||||
@@ -380,8 +380,8 @@ CurlInputStream::InitEasy()
|
||||
StringFormat<1024>("%s:%s", proxy_user,
|
||||
proxy_password).c_str());
|
||||
|
||||
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
|
||||
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
|
||||
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L);
|
||||
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L);
|
||||
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
||||
}
|
||||
|
||||
|
@@ -102,7 +102,7 @@ public:
|
||||
|
||||
~TidalSessionManager() noexcept;
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return defer_invoke_handlers.GetEventLoop();
|
||||
}
|
||||
|
||||
|
@@ -40,15 +40,15 @@ namespace Java {
|
||||
*/
|
||||
typedef LocalRef<jobject> LocalObject;
|
||||
|
||||
class Object : public GlobalRef<jobject> {
|
||||
class GlobalObject : public GlobalRef<jobject> {
|
||||
public:
|
||||
/**
|
||||
* Constructs an uninitialized object. The method
|
||||
* set() must be called before it is destructed.
|
||||
*/
|
||||
Object() = default;
|
||||
GlobalObject() = default;
|
||||
|
||||
Object(JNIEnv *env, jobject obj) noexcept
|
||||
GlobalObject(JNIEnv *env, jobject obj) noexcept
|
||||
:GlobalRef<jobject>(env, obj) {}
|
||||
};
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2016-2018 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
|
||||
@@ -90,6 +90,82 @@ public:
|
||||
throw std::runtime_error(curl_easy_strerror(code));
|
||||
}
|
||||
|
||||
void SetPrivate(void *pointer) {
|
||||
SetOption(CURLOPT_PRIVATE, pointer);
|
||||
}
|
||||
|
||||
void SetErrorBuffer(char *buf) {
|
||||
SetOption(CURLOPT_ERRORBUFFER, buf);
|
||||
}
|
||||
|
||||
void SetURL(const char *value) {
|
||||
SetOption(CURLOPT_URL, value);
|
||||
}
|
||||
|
||||
void SetUserAgent(const char *value) {
|
||||
SetOption(CURLOPT_USERAGENT, value);
|
||||
}
|
||||
|
||||
void SetRequestHeaders(struct curl_slist *headers) {
|
||||
SetOption(CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
|
||||
void SetBasicAuth(const char *userpwd) {
|
||||
SetOption(CURLOPT_USERPWD, userpwd);
|
||||
}
|
||||
|
||||
void SetNoProgress(bool value=true) {
|
||||
SetOption(CURLOPT_NOPROGRESS, (long)value);
|
||||
}
|
||||
|
||||
void SetNoSignal(bool value=true) {
|
||||
SetOption(CURLOPT_NOSIGNAL, (long)value);
|
||||
}
|
||||
|
||||
void SetFailOnError(bool value=true) {
|
||||
SetOption(CURLOPT_FAILONERROR, (long)value);
|
||||
}
|
||||
|
||||
void SetConnectTimeout(long timeout) {
|
||||
SetOption(CURLOPT_CONNECTTIMEOUT, timeout);
|
||||
}
|
||||
|
||||
void SetHeaderFunction(size_t (*function)(char *buffer, size_t size,
|
||||
size_t nitems,
|
||||
void *userdata),
|
||||
void *userdata) {
|
||||
SetOption(CURLOPT_HEADERFUNCTION, function);
|
||||
SetOption(CURLOPT_HEADERDATA, userdata);
|
||||
}
|
||||
|
||||
void SetWriteFunction(size_t (*function)(char *ptr, size_t size,
|
||||
size_t nmemb, void *userdata),
|
||||
void *userdata) {
|
||||
SetOption(CURLOPT_WRITEFUNCTION, function);
|
||||
SetOption(CURLOPT_WRITEDATA, userdata);
|
||||
}
|
||||
|
||||
void SetNoBody(bool value=true) {
|
||||
SetOption(CURLOPT_NOBODY, (long)value);
|
||||
}
|
||||
|
||||
void SetPost(bool value=true) {
|
||||
SetOption(CURLOPT_POST, (long)value);
|
||||
}
|
||||
|
||||
void SetRequestBody(const void *data, size_t size) {
|
||||
SetOption(CURLOPT_POSTFIELDS, data);
|
||||
SetOption(CURLOPT_POSTFIELDSIZE, (long)size);
|
||||
}
|
||||
|
||||
void SetHttpPost(const struct curl_httppost *post) {
|
||||
SetOption(CURLOPT_HTTPPOST, post);
|
||||
}
|
||||
|
||||
bool Unpause() noexcept {
|
||||
return ::curl_easy_pause(handle, CURLPAUSE_CONT) == CURLE_OK;
|
||||
}
|
||||
|
||||
CurlString Escape(const char *string, int length=0) const noexcept {
|
||||
return CurlString(curl_easy_escape(handle, string, length));
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@ public:
|
||||
CurlSocket(CurlGlobal &_global, EventLoop &_loop, SocketDescriptor _fd)
|
||||
:SocketMonitor(_fd, _loop), global(_global) {}
|
||||
|
||||
~CurlSocket() {
|
||||
~CurlSocket() noexcept {
|
||||
/* TODO: sometimes, CURL uses CURL_POLL_REMOVE after
|
||||
closing the socket, and sometimes, it uses
|
||||
CURL_POLL_REMOVE just to move the (still open)
|
||||
@@ -109,7 +109,8 @@ CurlGlobal::CurlGlobal(EventLoop &_loop)
|
||||
int
|
||||
CurlSocket::SocketFunction(gcc_unused CURL *easy,
|
||||
curl_socket_t s, int action,
|
||||
void *userp, void *socketp) noexcept {
|
||||
void *userp, void *socketp) noexcept
|
||||
{
|
||||
auto &global = *(CurlGlobal *)userp;
|
||||
CurlSocket *cs = (CurlSocket *)socketp;
|
||||
|
||||
@@ -153,11 +154,6 @@ CurlSocket::OnSocketReady(unsigned flags) noexcept
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the I/O thread. No lock needed.
|
||||
*
|
||||
* Throws std::runtime_error on error.
|
||||
*/
|
||||
void
|
||||
CurlGlobal::Add(CURL *easy, CurlRequest &request)
|
||||
{
|
||||
@@ -194,11 +190,6 @@ ToRequest(CURL *easy) noexcept
|
||||
return (CurlRequest *)p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for finished HTTP responses.
|
||||
*
|
||||
* Runs in the I/O thread. The caller must not hold locks.
|
||||
*/
|
||||
inline void
|
||||
CurlGlobal::ReadInfo() noexcept
|
||||
{
|
||||
@@ -217,6 +208,20 @@ CurlGlobal::ReadInfo() noexcept
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) noexcept
|
||||
{
|
||||
int running_handles;
|
||||
CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask,
|
||||
&running_handles);
|
||||
if (mcode != CURLM_OK)
|
||||
FormatError(curlm_domain,
|
||||
"curl_multi_socket_action() failed: %s",
|
||||
curl_multi_strerror(mcode));
|
||||
|
||||
defer_read_info.Schedule();
|
||||
}
|
||||
|
||||
inline void
|
||||
CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
|
||||
{
|
||||
@@ -236,11 +241,11 @@ CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
|
||||
}
|
||||
|
||||
int
|
||||
CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms,
|
||||
CurlGlobal::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms,
|
||||
void *userp) noexcept
|
||||
{
|
||||
auto &global = *(CurlGlobal *)userp;
|
||||
assert(_global == global.multi.Get());
|
||||
assert(_multi == global.multi.Get());
|
||||
|
||||
global.UpdateTimeout(timeout_ms);
|
||||
return 0;
|
||||
@@ -251,17 +256,3 @@ CurlGlobal::OnTimeout() noexcept
|
||||
{
|
||||
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
||||
}
|
||||
|
||||
void
|
||||
CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) noexcept
|
||||
{
|
||||
int running_handles;
|
||||
CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask,
|
||||
&running_handles);
|
||||
if (mcode != CURLM_OK)
|
||||
FormatError(curlm_domain,
|
||||
"curl_multi_socket_action() failed: %s",
|
||||
curl_multi_strerror(mcode));
|
||||
|
||||
defer_read_info.Schedule();
|
||||
}
|
||||
|
@@ -50,33 +50,33 @@ class CurlGlobal final {
|
||||
public:
|
||||
explicit CurlGlobal(EventLoop &_loop);
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return timeout_event.GetEventLoop();
|
||||
}
|
||||
|
||||
void Add(CURL *easy, CurlRequest &request);
|
||||
void Remove(CURL *easy) noexcept;
|
||||
|
||||
/**
|
||||
* Check for finished HTTP responses.
|
||||
*
|
||||
* Runs in the I/O thread. The caller must not hold locks.
|
||||
*/
|
||||
void ReadInfo() noexcept;
|
||||
|
||||
void Assign(curl_socket_t fd, CurlSocket &cs) noexcept {
|
||||
curl_multi_assign(multi.Get(), fd, &cs);
|
||||
}
|
||||
|
||||
void SocketAction(curl_socket_t fd, int ev_bitmask) noexcept;
|
||||
|
||||
void InvalidateSockets() {
|
||||
void InvalidateSockets() noexcept {
|
||||
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Check for finished HTTP responses.
|
||||
*
|
||||
* Runs in the I/O thread. The caller must not hold locks.
|
||||
*/
|
||||
void ReadInfo() noexcept;
|
||||
|
||||
void UpdateTimeout(long timeout_ms) noexcept;
|
||||
static int TimerFunction(CURLM *global, long timeout_ms,
|
||||
static int TimerFunction(CURLM *multi, long timeout_ms,
|
||||
void *userp) noexcept;
|
||||
|
||||
/* callback for #timeout_event */
|
||||
|
@@ -50,11 +50,19 @@ public:
|
||||
CurlInit(const CurlInit &) = delete;
|
||||
CurlInit &operator=(const CurlInit &) = delete;
|
||||
|
||||
CurlGlobal &operator*() {
|
||||
CurlGlobal &operator*() noexcept {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
CurlGlobal *operator->() {
|
||||
const CurlGlobal &operator*() const noexcept {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
CurlGlobal *operator->() noexcept {
|
||||
return instance;
|
||||
}
|
||||
|
||||
const CurlGlobal *operator->() const noexcept {
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
@@ -52,17 +52,17 @@ CurlRequest::CurlRequest(CurlGlobal &_global,
|
||||
{
|
||||
error_buffer[0] = 0;
|
||||
|
||||
easy.SetOption(CURLOPT_PRIVATE, (void *)this);
|
||||
easy.SetOption(CURLOPT_USERAGENT, "Music Player Daemon " VERSION);
|
||||
easy.SetOption(CURLOPT_HEADERFUNCTION, _HeaderFunction);
|
||||
easy.SetOption(CURLOPT_WRITEHEADER, this);
|
||||
easy.SetOption(CURLOPT_WRITEFUNCTION, WriteFunction);
|
||||
easy.SetOption(CURLOPT_WRITEDATA, this);
|
||||
easy.SetOption(CURLOPT_NETRC, 1l);
|
||||
easy.SetOption(CURLOPT_ERRORBUFFER, error_buffer);
|
||||
easy.SetOption(CURLOPT_NOPROGRESS, 1l);
|
||||
easy.SetOption(CURLOPT_NOSIGNAL, 1l);
|
||||
easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
|
||||
easy.SetPrivate((void *)this);
|
||||
easy.SetUserAgent("Music Player Daemon " VERSION);
|
||||
easy.SetHeaderFunction(_HeaderFunction, this);
|
||||
easy.SetWriteFunction(WriteFunction, this);
|
||||
#if !defined(ANDROID) && !defined(_WIN32)
|
||||
easy.SetOption(CURLOPT_NETRC, 1L);
|
||||
#endif
|
||||
easy.SetErrorBuffer(error_buffer);
|
||||
easy.SetNoProgress();
|
||||
easy.SetNoSignal();
|
||||
easy.SetConnectTimeout(10);
|
||||
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ CurlRequest::Resume() noexcept
|
||||
{
|
||||
assert(registered);
|
||||
|
||||
curl_easy_pause(easy.Get(), CURLPAUSE_CONT);
|
||||
easy.Unpause();
|
||||
|
||||
global.InvalidateSockets();
|
||||
}
|
||||
@@ -220,14 +220,14 @@ CurlRequest::HeaderFunction(StringView s) noexcept
|
||||
}
|
||||
|
||||
size_t
|
||||
CurlRequest::_HeaderFunction(void *ptr, size_t size, size_t nmemb,
|
||||
CurlRequest::_HeaderFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlRequest &c = *(CurlRequest *)stream;
|
||||
|
||||
size *= nmemb;
|
||||
|
||||
c.HeaderFunction({(const char *)ptr, size});
|
||||
c.HeaderFunction({ptr, size});
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ CurlRequest::DataReceived(const void *ptr, size_t received_size) noexcept
|
||||
}
|
||||
|
||||
size_t
|
||||
CurlRequest::WriteFunction(void *ptr, size_t size, size_t nmemb,
|
||||
CurlRequest::WriteFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlRequest &c = *(CurlRequest *)stream;
|
||||
|
@@ -127,7 +127,7 @@ public:
|
||||
}
|
||||
|
||||
void SetUrl(const char *url) {
|
||||
easy.SetOption(CURLOPT_URL, url);
|
||||
easy.SetURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,11 +160,11 @@ private:
|
||||
void OnPostponeError() noexcept;
|
||||
|
||||
/** called by curl when new data is available */
|
||||
static size_t _HeaderFunction(void *ptr, size_t size, size_t nmemb,
|
||||
static size_t _HeaderFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
|
||||
/** called by curl when new data is available */
|
||||
static size_t WriteFunction(void *ptr, size_t size, size_t nmemb,
|
||||
static size_t WriteFunction(char *ptr, size_t size, size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
};
|
||||
|
||||
|
@@ -42,7 +42,7 @@ class CurlSlist {
|
||||
struct curl_slist *head = nullptr;
|
||||
|
||||
public:
|
||||
CurlSlist() = default;
|
||||
CurlSlist() noexcept = default;
|
||||
|
||||
CurlSlist(CurlSlist &&src) noexcept
|
||||
:head(std::exchange(src.head, nullptr)) {}
|
||||
|
@@ -42,7 +42,7 @@ public:
|
||||
DisconnectIndirect();
|
||||
}
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return watch.GetEventLoop();
|
||||
}
|
||||
|
||||
|
@@ -86,7 +86,7 @@ struct WrapVariant : BasicValue<T> {
|
||||
template<typename T>
|
||||
static WrapVariant<T> Variant(const T &_value) noexcept {
|
||||
return WrapVariant<T>(_value);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct WrapFixedArray {
|
||||
@@ -103,7 +103,7 @@ template<typename T>
|
||||
static WrapFixedArray<T> FixedArray(const T *_data,
|
||||
size_t _size) noexcept {
|
||||
return WrapFixedArray<T>(_data, _size);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
struct WrapStruct {
|
||||
@@ -118,7 +118,7 @@ struct WrapStruct {
|
||||
template<typename... T>
|
||||
static WrapStruct<T...> Struct(const T&... values) noexcept {
|
||||
return WrapStruct<T...>(values...);
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ODBus */
|
||||
|
||||
|
@@ -102,7 +102,7 @@ public:
|
||||
|
||||
void Shutdown() noexcept;
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return defer_dispatch.GetEventLoop();
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,7 @@ FfmpegTimeToDouble(int64_t t, const AVRational time_base) noexcept
|
||||
{
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return FloatDuration(av_rescale_q(t, time_base, (AVRational){1, 1024}))
|
||||
return FloatDuration(av_rescale_q(t, time_base, {1, 1024}))
|
||||
/ 1024;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ FromFfmpegTime(int64_t t, const AVRational time_base) noexcept
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return SongTime::FromMS(av_rescale_q(t, time_base,
|
||||
(AVRational){1, 1000}));
|
||||
{1, 1000}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -36,11 +36,6 @@
|
||||
#include <ctype.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32.hxx"
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <assert.h>
|
||||
@@ -59,7 +54,7 @@ try {
|
||||
if (u.IsNull())
|
||||
return AllocatedString<>::Duplicate(src);
|
||||
|
||||
AllocatedArray<UChar> folded(u.size() * 2u);
|
||||
AllocatedArray<UChar> folded(u.size() * 2U);
|
||||
|
||||
UErrorCode error_code = U_ZERO_ERROR;
|
||||
size_t folded_length = u_strFoldCase(folded.begin(), folded.size(),
|
||||
@@ -72,25 +67,6 @@ try {
|
||||
folded.SetSize(folded_length);
|
||||
return UCharToUTF8({folded.begin(), folded.size()});
|
||||
|
||||
#elif defined(_WIN32)
|
||||
const auto u = MultiByteToWideChar(CP_UTF8, src);
|
||||
|
||||
const int size = LCMapStringEx(LOCALE_NAME_INVARIANT,
|
||||
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
|
||||
u.c_str(), -1, nullptr, 0,
|
||||
nullptr, nullptr, 0);
|
||||
if (size <= 0)
|
||||
return AllocatedString<>::Duplicate(src);
|
||||
|
||||
std::unique_ptr<wchar_t[]> buffer(new wchar_t[size]);
|
||||
if (LCMapStringEx(LOCALE_NAME_INVARIANT,
|
||||
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
|
||||
u.c_str(), -1, buffer.get(), size,
|
||||
nullptr, nullptr, 0) <= 0)
|
||||
return AllocatedString<>::Duplicate(src);
|
||||
|
||||
return WideCharToMultiByte(CP_UTF8, buffer.get());
|
||||
|
||||
#else
|
||||
#error not implemented
|
||||
#endif
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if defined(HAVE_ICU) || defined(_WIN32)
|
||||
#ifdef HAVE_ICU
|
||||
#define HAVE_ICU_CASE_FOLD
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
@@ -109,7 +109,7 @@ IcuCollate(const char *a, const char *b) noexcept
|
||||
}
|
||||
|
||||
auto result = CompareStringEx(LOCALE_NAME_INVARIANT,
|
||||
LINGUISTIC_IGNORECASE,
|
||||
NORM_IGNORECASE,
|
||||
wa.c_str(), -1,
|
||||
wb.c_str(), -1,
|
||||
nullptr, nullptr, 0);
|
||||
|
@@ -22,6 +22,11 @@
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "config.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32.hxx"
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_ICU_CASE_FOLD
|
||||
@@ -29,6 +34,17 @@
|
||||
IcuCompare::IcuCompare(const char *_needle) noexcept
|
||||
:needle(IcuCaseFold(_needle)) {}
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
IcuCompare::IcuCompare(const char *_needle) noexcept
|
||||
:needle(nullptr)
|
||||
{
|
||||
try {
|
||||
needle = MultiByteToWideChar(CP_UTF8, _needle);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
IcuCompare::IcuCompare(const char *_needle) noexcept
|
||||
@@ -41,6 +57,22 @@ IcuCompare::operator==(const char *haystack) const noexcept
|
||||
{
|
||||
#ifdef HAVE_ICU_CASE_FOLD
|
||||
return StringIsEqual(IcuCaseFold(haystack).c_str(), needle.c_str());
|
||||
#elif defined(_WIN32)
|
||||
if (needle.IsNull())
|
||||
/* the MultiByteToWideChar() call in the constructor
|
||||
has failed, so let's always fail the comparison */
|
||||
return false;
|
||||
|
||||
try {
|
||||
auto w_haystack = MultiByteToWideChar(CP_UTF8, haystack);
|
||||
return CompareStringEx(LOCALE_NAME_INVARIANT,
|
||||
NORM_IGNORECASE,
|
||||
w_haystack.c_str(), -1,
|
||||
needle.c_str(), -1,
|
||||
nullptr, nullptr, 0) == CSTR_EQUAL;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return strcasecmp(haystack, needle.c_str());
|
||||
#endif
|
||||
@@ -52,6 +84,24 @@ IcuCompare::IsIn(const char *haystack) const noexcept
|
||||
#ifdef HAVE_ICU_CASE_FOLD
|
||||
return StringFind(IcuCaseFold(haystack).c_str(),
|
||||
needle.c_str()) != nullptr;
|
||||
#elif defined(_WIN32)
|
||||
if (needle.IsNull())
|
||||
/* the MultiByteToWideChar() call in the constructor
|
||||
has failed, so let's always fail the comparison */
|
||||
return false;
|
||||
|
||||
try {
|
||||
auto w_haystack = MultiByteToWideChar(CP_UTF8, haystack);
|
||||
return FindNLSStringEx(LOCALE_NAME_INVARIANT,
|
||||
FIND_FROMSTART|NORM_IGNORECASE,
|
||||
w_haystack.c_str(), -1,
|
||||
needle.c_str(), -1,
|
||||
nullptr,
|
||||
nullptr, nullptr, 0) >= 0;
|
||||
} catch (...) {
|
||||
/* MultiByteToWideChar() has failed */
|
||||
return false;
|
||||
}
|
||||
#elif defined(HAVE_STRCASESTR)
|
||||
return strcasestr(haystack, needle.c_str()) != nullptr;
|
||||
#else
|
||||
|
@@ -23,13 +23,23 @@
|
||||
#include "util/Compiler.h"
|
||||
#include "util/AllocatedString.hxx"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wchar.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This class can compare one string ("needle") with lots of other
|
||||
* strings ("haystacks") efficiently, ignoring case. With some
|
||||
* configurations, it can prepare a case-folded version of the needle.
|
||||
*/
|
||||
class IcuCompare {
|
||||
#ifdef _WIN32
|
||||
/* Windows API functions work with wchar_t strings, so let's
|
||||
cache the MultiByteToWideChar() result for performance */
|
||||
AllocatedString<wchar_t> needle;
|
||||
#else
|
||||
AllocatedString<> needle;
|
||||
#endif
|
||||
|
||||
public:
|
||||
IcuCompare():needle(nullptr) {}
|
||||
@@ -38,12 +48,12 @@ public:
|
||||
|
||||
IcuCompare(const IcuCompare &src) noexcept
|
||||
:needle(src
|
||||
? AllocatedString<>::Duplicate(src.needle.c_str())
|
||||
? src.needle.Clone()
|
||||
: nullptr) {}
|
||||
|
||||
IcuCompare &operator=(const IcuCompare &src) noexcept {
|
||||
needle = src
|
||||
? AllocatedString<>::Duplicate(src.needle.c_str())
|
||||
? src.needle.Clone()
|
||||
: nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
@@ -191,7 +191,9 @@ static constexpr int
|
||||
events_to_libnfs(unsigned i) noexcept
|
||||
{
|
||||
return ((i & SocketMonitor::READ) ? POLLIN : 0) |
|
||||
((i & SocketMonitor::WRITE) ? POLLOUT : 0);
|
||||
((i & SocketMonitor::WRITE) ? POLLOUT : 0) |
|
||||
((i & SocketMonitor::HANGUP) ? POLLHUP : 0) |
|
||||
((i & SocketMonitor::ERROR) ? POLLERR : 0);
|
||||
}
|
||||
|
||||
NfsConnection::~NfsConnection() noexcept
|
||||
@@ -450,8 +452,7 @@ NfsConnection::ScheduleSocket() noexcept
|
||||
SocketMonitor::Open(_fd);
|
||||
}
|
||||
|
||||
SocketMonitor::Schedule(libnfs_to_events(which_events)
|
||||
| SocketMonitor::HANGUP);
|
||||
SocketMonitor::Schedule(libnfs_to_events(which_events));
|
||||
}
|
||||
|
||||
inline int
|
||||
|
@@ -161,9 +161,7 @@ public:
|
||||
return export_name.c_str();
|
||||
}
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
return SocketMonitor::GetEventLoop();
|
||||
}
|
||||
using SocketMonitor::GetEventLoop;
|
||||
|
||||
/**
|
||||
* Ensure that the connection is established. The connection
|
||||
|
@@ -180,7 +180,6 @@ NfsFileReader::OnNfsConnectionDisconnected(std::exception_ptr e) noexcept
|
||||
inline void
|
||||
NfsFileReader::OpenCallback(nfsfh *_fh) noexcept
|
||||
{
|
||||
assert(state == State::OPEN);
|
||||
assert(connection != nullptr);
|
||||
assert(_fh != nullptr);
|
||||
|
||||
@@ -197,27 +196,33 @@ NfsFileReader::OpenCallback(nfsfh *_fh) noexcept
|
||||
}
|
||||
|
||||
inline void
|
||||
NfsFileReader::StatCallback(const struct stat *st) noexcept
|
||||
NfsFileReader::StatCallback(const struct stat *_st) noexcept
|
||||
{
|
||||
assert(state == State::STAT);
|
||||
assert(connection != nullptr);
|
||||
assert(fh != nullptr);
|
||||
assert(st != nullptr);
|
||||
assert(_st != nullptr);
|
||||
|
||||
#if defined(_WIN32) && !defined(_WIN64)
|
||||
/* on 32-bit Windows, libnfs enables -D_FILE_OFFSET_BITS=64,
|
||||
but MPD (Meson) doesn't - to work around this mismatch, we
|
||||
cast explicitly to "struct stat64" */
|
||||
const auto *st = (const struct stat64 *)_st;
|
||||
#else
|
||||
const auto *st = _st;
|
||||
#endif
|
||||
|
||||
if (!S_ISREG(st->st_mode)) {
|
||||
OnNfsFileError(std::make_exception_ptr(std::runtime_error("Not a regular file")));
|
||||
return;
|
||||
}
|
||||
|
||||
state = State::IDLE;
|
||||
|
||||
OnNfsFileOpen(st->st_size);
|
||||
}
|
||||
|
||||
void
|
||||
NfsFileReader::OnNfsCallback(unsigned status, void *data) noexcept
|
||||
{
|
||||
switch (state) {
|
||||
switch (std::exchange(state, State::IDLE)) {
|
||||
case State::INITIAL:
|
||||
case State::DEFER:
|
||||
case State::MOUNT:
|
||||
@@ -234,7 +239,6 @@ NfsFileReader::OnNfsCallback(unsigned status, void *data) noexcept
|
||||
break;
|
||||
|
||||
case State::READ:
|
||||
state = State::IDLE;
|
||||
OnNfsFileRead(data, status);
|
||||
break;
|
||||
}
|
||||
|
@@ -69,7 +69,7 @@ public:
|
||||
NfsFileReader() noexcept;
|
||||
~NfsFileReader() noexcept;
|
||||
|
||||
EventLoop &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return defer_open.GetEventLoop();
|
||||
}
|
||||
|
||||
|
@@ -41,6 +41,6 @@ public:
|
||||
LockGuard &operator=(const LockGuard &) = delete;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -272,7 +272,7 @@ UPnPDeviceDirectory::~UPnPDeviceDirectory() noexcept
|
||||
}
|
||||
|
||||
inline EventLoop &
|
||||
UPnPDeviceDirectory::GetEventLoop() noexcept
|
||||
UPnPDeviceDirectory::GetEventLoop() const noexcept
|
||||
{
|
||||
return curl->GetEventLoop();
|
||||
}
|
||||
|
@@ -168,7 +168,7 @@ public:
|
||||
UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete;
|
||||
UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete;
|
||||
|
||||
EventLoop &GetEventLoop() noexcept;
|
||||
EventLoop &GetEventLoop() const noexcept;
|
||||
|
||||
void Start();
|
||||
|
||||
|
@@ -30,6 +30,6 @@ namespace ixmlwrap {
|
||||
const char *getFirstElementValue(IXML_Document *doc,
|
||||
const char *name) noexcept;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* _IXMLWRAP_H_INCLUDED_ */
|
||||
|
@@ -29,6 +29,7 @@ struct MixerPlugin;
|
||||
|
||||
extern const MixerPlugin null_mixer_plugin;
|
||||
extern const MixerPlugin software_mixer_plugin;
|
||||
extern const MixerPlugin android_mixer_plugin;
|
||||
extern const MixerPlugin alsa_mixer_plugin;
|
||||
extern const MixerPlugin haiku_mixer_plugin;
|
||||
extern const MixerPlugin oss_mixer_plugin;
|
||||
|
116
src/mixer/plugins/AndroidMixerPlugin.cxx
Normal file
116
src/mixer/plugins/AndroidMixerPlugin.cxx
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 "mixer/MixerInternal.hxx"
|
||||
#include "filter/plugins/VolumeFilterPlugin.hxx"
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "android/Context.hxx"
|
||||
#include "android/AudioManager.hxx"
|
||||
|
||||
#include "Main.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
class AndroidMixer final : public Mixer {
|
||||
AudioManager *audioManager;
|
||||
int currentVolume;
|
||||
int maxAndroidVolume;
|
||||
int lastAndroidVolume;
|
||||
public:
|
||||
explicit AndroidMixer(MixerListener &_listener);
|
||||
|
||||
~AndroidMixer() override;
|
||||
|
||||
/* virtual methods from class Mixer */
|
||||
void Open() override {
|
||||
}
|
||||
|
||||
void Close() noexcept override {
|
||||
}
|
||||
|
||||
int GetVolume() override;
|
||||
|
||||
void SetVolume(unsigned volume) override;
|
||||
};
|
||||
|
||||
static Mixer *
|
||||
android_mixer_init([[maybe_unused]] EventLoop &event_loop,
|
||||
[[maybe_unused]] AudioOutput &ao,
|
||||
MixerListener &listener,
|
||||
[[maybe_unused]] const ConfigBlock &block)
|
||||
{
|
||||
return new AndroidMixer(listener);
|
||||
}
|
||||
|
||||
AndroidMixer::AndroidMixer(MixerListener &_listener)
|
||||
:Mixer(android_mixer_plugin, _listener)
|
||||
{
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
audioManager = context->GetAudioManager(env);
|
||||
|
||||
maxAndroidVolume = audioManager->GetMaxVolume();
|
||||
if (maxAndroidVolume != 0)
|
||||
{
|
||||
lastAndroidVolume = audioManager->GetVolume(env);
|
||||
currentVolume = 100 * lastAndroidVolume / maxAndroidVolume;
|
||||
}
|
||||
}
|
||||
|
||||
AndroidMixer::~AndroidMixer()
|
||||
{
|
||||
delete audioManager;
|
||||
}
|
||||
|
||||
int
|
||||
AndroidMixer::GetVolume()
|
||||
{
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
if (maxAndroidVolume == 0)
|
||||
return -1;
|
||||
|
||||
// The android volume index (or scale) is very likely inferior to the
|
||||
// MPD one (100). The last volume set by MPD is saved into
|
||||
// currentVolume, this volume is returned instead of the Android one
|
||||
// when the Android mixer was not touched by an other application. This
|
||||
// allows to fake a 0..100 scale from MPD.
|
||||
|
||||
int volume = audioManager->GetVolume(env);
|
||||
if (volume == lastAndroidVolume)
|
||||
return currentVolume;
|
||||
|
||||
return 100 * volume / maxAndroidVolume;
|
||||
}
|
||||
|
||||
void
|
||||
AndroidMixer::SetVolume(unsigned newVolume)
|
||||
{
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
if (maxAndroidVolume == 0)
|
||||
return;
|
||||
currentVolume = newVolume;
|
||||
lastAndroidVolume = currentVolume * maxAndroidVolume / 100;
|
||||
audioManager->SetVolume(env, lastAndroidVolume);
|
||||
|
||||
}
|
||||
|
||||
const MixerPlugin android_mixer_plugin = {
|
||||
android_mixer_init,
|
||||
true,
|
||||
};
|
@@ -20,13 +20,13 @@
|
||||
#include "mixer/MixerInternal.hxx"
|
||||
#include "output/OutputAPI.hxx"
|
||||
#include "output/plugins/WinmmOutputPlugin.hxx"
|
||||
#include "util/Math.hxx"
|
||||
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <windows.h>
|
||||
|
||||
class WinmmMixer final : public Mixer {
|
||||
|
@@ -34,6 +34,10 @@ if is_windows
|
||||
mixer_plugins_sources += 'WinmmMixerPlugin.cxx'
|
||||
endif
|
||||
|
||||
if is_android
|
||||
mixer_plugins_sources += 'AndroidMixerPlugin.cxx'
|
||||
endif
|
||||
|
||||
mixer_plugins = static_library(
|
||||
'mixer_plugins',
|
||||
mixer_plugins_sources,
|
||||
|
@@ -70,7 +70,7 @@ public:
|
||||
NeighborListener &_listener) noexcept
|
||||
:NeighborExplorer(_listener), event_loop(_event_loop) {}
|
||||
|
||||
auto &GetEventLoop() noexcept {
|
||||
auto &GetEventLoop() const noexcept {
|
||||
return event_loop;
|
||||
}
|
||||
|
||||
|
@@ -395,7 +395,7 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
|
||||
#endif
|
||||
buffer_time(block.GetPositiveValue("buffer_time",
|
||||
MPD_ALSA_BUFFER_TIME_US)),
|
||||
period_time(block.GetPositiveValue("period_time", 0u))
|
||||
period_time(block.GetPositiveValue("period_time", 0U))
|
||||
{
|
||||
#ifdef SND_PCM_NO_AUTO_RESAMPLE
|
||||
if (!block.GetBlockValue("auto_resample", true))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user