Compare commits
65 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 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@
|
||||
/output/
|
||||
|
||||
__pycache__/
|
||||
|
||||
/.clangd/
|
||||
/compile_commands.json
|
||||
|
||||
@@ -135,7 +135,8 @@ jobs:
|
||||
- chromaprint
|
||||
- libsamplerate
|
||||
- libsoxr
|
||||
- libzzip
|
||||
# libzzip appears to be broken on Homebrew: "ld: library not found for -lzzip"
|
||||
#- libzzip
|
||||
- flac
|
||||
- opus
|
||||
- libvorbis
|
||||
|
||||
38
NEWS
38
NEWS
@@ -1,3 +1,41 @@
|
||||
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
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="45"
|
||||
android:versionName="0.21.22">
|
||||
android:versionCode="47"
|
||||
android:versionName="0.21.24">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.22'
|
||||
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
|
||||
|
||||
16
meson.build
16
meson.build
@@ -1,11 +1,12 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.22',
|
||||
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)
|
||||
|
||||
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,6 +4,7 @@ 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
|
||||
@@ -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.69.1.tar.xz',
|
||||
'03c7d5e6697f7b7e40ada1b2256e565a555657398e6c1fcfa4cb251ccd819d4f',
|
||||
'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>
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,10 +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"
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
@@ -76,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 != '.')
|
||||
@@ -99,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;
|
||||
}
|
||||
|
||||
@@ -185,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;
|
||||
@@ -299,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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,6 +90,11 @@ public:
|
||||
constexpr
|
||||
#endif
|
||||
operator Path() const noexcept {
|
||||
#ifdef _UNICODE
|
||||
if (value.IsNull())
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global,
|
||||
easy.SetUserAgent("Music Player Daemon " VERSION);
|
||||
easy.SetHeaderFunction(_HeaderFunction, this);
|
||||
easy.SetWriteFunction(WriteFunction, this);
|
||||
#ifndef ANDROID
|
||||
#if !defined(ANDROID) && !defined(_WIN32)
|
||||
easy.SetOption(CURLOPT_NETRC, 1L);
|
||||
#endif
|
||||
easy.SetErrorBuffer(error_buffer);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,6 @@ public:
|
||||
LockGuard &operator=(const LockGuard &) = delete;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,6 @@ namespace ixmlwrap {
|
||||
const char *getFirstElementValue(IXML_Document *doc,
|
||||
const char *name) noexcept;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* _IXMLWRAP_H_INCLUDED_ */
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#else
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
/* on macOS, OpenAL is deprecated, but since the user asked to enable
|
||||
this plugin, let's ignore the compiler warnings */
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
class OpenALOutput final : AudioOutput {
|
||||
|
||||
@@ -21,6 +21,28 @@
|
||||
#include "PcmBuffer.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
|
||||
/*
|
||||
* According to:
|
||||
* - https://xiph.org/flac/format.html#frame_header
|
||||
* - https://github.com/nu774/qaac/wiki/Multichannel--handling
|
||||
* the source channel order (after decoding, e.g., flac, alac) is for
|
||||
* - 1ch: mono
|
||||
* - 2ch: left, right
|
||||
* - 3ch: left, right, center
|
||||
* - 4ch: front left, front right, back left, back right
|
||||
* - 5ch: front left, front right, front center, back/surround left, back/surround right
|
||||
* - 6ch (aka 5.1): front left, front right, front center, LFE, back/surround left, back/surround right
|
||||
* - 7ch: front left, front right, front center, LFE, back center, side left, side right
|
||||
* - 8ch: (aka 7.1): front left, front right, front center, LFE, back left, back right, side left, side right
|
||||
*
|
||||
* The ALSA default channel map is (see /usr/share/alsa/pcm/surround71.conf):
|
||||
* - front left, front right, back left, back right, front center, LFE, side left, side right
|
||||
*
|
||||
* Hence, in case of the following source channel orders 3ch, 5ch, 6ch (aka
|
||||
* 5.1), 7ch and 8ch the channel order has to be adapted
|
||||
*/
|
||||
|
||||
template<typename V>
|
||||
struct TwoPointers {
|
||||
V *dest;
|
||||
@@ -44,17 +66,57 @@ struct TwoPointers {
|
||||
return *this;
|
||||
}
|
||||
|
||||
TwoPointers<V> &ToAlsa50() noexcept {
|
||||
*dest++ = src[0]; // front left
|
||||
*dest++ = src[1]; // front right
|
||||
*dest++ = src[3]; // surround left
|
||||
*dest++ = src[4]; // surround right
|
||||
*dest++ = src[2]; // front center
|
||||
src += 5;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TwoPointers<V> &ToAlsa51() noexcept {
|
||||
return CopyTwo() // left+right
|
||||
.SwapTwoPairs(); // center, LFE, surround left+right
|
||||
}
|
||||
|
||||
TwoPointers<V> &ToAlsa70() noexcept {
|
||||
*dest++ = src[0]; // front left
|
||||
*dest++ = src[1]; // front right
|
||||
*dest++ = src[5]; // side left
|
||||
*dest++ = src[6]; // side right
|
||||
*dest++ = src[2]; // front center
|
||||
*dest++ = src[3]; // LFE
|
||||
*dest++ = src[4]; // back center
|
||||
src += 7;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TwoPointers<V> &ToAlsa71() noexcept {
|
||||
return ToAlsa51()
|
||||
.CopyTwo(); // side left+right
|
||||
}
|
||||
};
|
||||
|
||||
template<typename V>
|
||||
static void
|
||||
ToAlsaChannelOrder50(V *dest, const V *src, size_t n) noexcept
|
||||
{
|
||||
TwoPointers<V> p{dest, src};
|
||||
for (size_t i = 0; i != n; ++i)
|
||||
p.ToAlsa50();
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
static inline ConstBuffer<V>
|
||||
ToAlsaChannelOrder50(PcmBuffer &buffer, ConstBuffer<V> src) noexcept
|
||||
{
|
||||
auto dest = buffer.GetT<V>(src.size);
|
||||
ToAlsaChannelOrder50(dest, src.data, src.size / 5);
|
||||
return { dest, src.size };
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
static void
|
||||
ToAlsaChannelOrder51(V *dest, const V *src, size_t n) noexcept
|
||||
@@ -73,6 +135,24 @@ ToAlsaChannelOrder51(PcmBuffer &buffer, ConstBuffer<V> src) noexcept
|
||||
return { dest, src.size };
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
static void
|
||||
ToAlsaChannelOrder70(V *dest, const V *src, size_t n) noexcept
|
||||
{
|
||||
TwoPointers<V> p{dest, src};
|
||||
for (size_t i = 0; i != n; ++i)
|
||||
p.ToAlsa70();
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
static inline ConstBuffer<V>
|
||||
ToAlsaChannelOrder70(PcmBuffer &buffer, ConstBuffer<V> src) noexcept
|
||||
{
|
||||
auto dest = buffer.GetT<V>(src.size);
|
||||
ToAlsaChannelOrder70(dest, src.data, src.size / 7);
|
||||
return { dest, src.size };
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
static void
|
||||
ToAlsaChannelOrder71(V *dest, const V *src, size_t n) noexcept
|
||||
@@ -97,9 +177,15 @@ ToAlsaChannelOrderT(PcmBuffer &buffer, ConstBuffer<V> src,
|
||||
unsigned channels) noexcept
|
||||
{
|
||||
switch (channels) {
|
||||
case 5: // 5.0
|
||||
return ToAlsaChannelOrder50(buffer, src);
|
||||
|
||||
case 6: // 5.1
|
||||
return ToAlsaChannelOrder51(buffer, src);
|
||||
|
||||
case 7: // 7.0
|
||||
return ToAlsaChannelOrder70(buffer, src);
|
||||
|
||||
case 8: // 7.1
|
||||
return ToAlsaChannelOrder71(buffer, src);
|
||||
|
||||
|
||||
@@ -223,7 +223,8 @@ private:
|
||||
*
|
||||
* Caller must lock the mutex.
|
||||
*/
|
||||
void StartDecoder(std::shared_ptr<MusicPipe> pipe) noexcept;
|
||||
void StartDecoder(std::shared_ptr<MusicPipe> pipe,
|
||||
bool initial_seek_essential) noexcept;
|
||||
|
||||
/**
|
||||
* The decoder has acknowledged the "START" command (see
|
||||
@@ -364,7 +365,8 @@ public:
|
||||
};
|
||||
|
||||
void
|
||||
Player::StartDecoder(std::shared_ptr<MusicPipe> _pipe) noexcept
|
||||
Player::StartDecoder(std::shared_ptr<MusicPipe> _pipe,
|
||||
bool initial_seek_essential) noexcept
|
||||
{
|
||||
assert(queued || pc.command == PlayerCommand::SEEK);
|
||||
assert(pc.next_song != nullptr);
|
||||
@@ -376,6 +378,7 @@ Player::StartDecoder(std::shared_ptr<MusicPipe> _pipe) noexcept
|
||||
|
||||
dc.Start(std::make_unique<DetachedSong>(*pc.next_song),
|
||||
start_time, pc.next_song->GetEndTime(),
|
||||
initial_seek_essential,
|
||||
buffer, std::move(_pipe));
|
||||
}
|
||||
|
||||
@@ -633,7 +636,7 @@ Player::SeekDecoder() noexcept
|
||||
pipe->Clear();
|
||||
|
||||
/* re-start the decoder */
|
||||
StartDecoder(pipe);
|
||||
StartDecoder(pipe, true);
|
||||
ActivateDecoder();
|
||||
|
||||
pc.seeking = true;
|
||||
@@ -711,7 +714,7 @@ Player::ProcessCommand() noexcept
|
||||
pc.CommandFinished();
|
||||
|
||||
if (dc.IsIdle())
|
||||
StartDecoder(std::make_shared<MusicPipe>());
|
||||
StartDecoder(std::make_shared<MusicPipe>(), false);
|
||||
|
||||
break;
|
||||
|
||||
@@ -964,6 +967,12 @@ Player::SongBorder() noexcept
|
||||
if (border_pause) {
|
||||
paused = true;
|
||||
pc.listener.OnBorderPause();
|
||||
|
||||
/* drain all outputs to guarantee the current song is
|
||||
really being played to the end; without this, the
|
||||
Pause() call would drop all ring buffers */
|
||||
pc.outputs.Drain();
|
||||
|
||||
pc.outputs.Pause();
|
||||
idle_add(IDLE_PLAYER);
|
||||
}
|
||||
@@ -976,7 +985,7 @@ Player::Run() noexcept
|
||||
|
||||
const std::lock_guard<Mutex> lock(pc.mutex);
|
||||
|
||||
StartDecoder(pipe);
|
||||
StartDecoder(pipe, true);
|
||||
ActivateDecoder();
|
||||
|
||||
pc.state = PlayerState::PLAY;
|
||||
@@ -1016,7 +1025,7 @@ Player::Run() noexcept
|
||||
|
||||
assert(dc.pipe == nullptr || dc.pipe == pipe);
|
||||
|
||||
StartDecoder(std::make_shared<MusicPipe>());
|
||||
StartDecoder(std::make_shared<MusicPipe>(), false);
|
||||
}
|
||||
|
||||
if (/* no cross-fading if MPD is going to pause at the
|
||||
|
||||
@@ -33,9 +33,16 @@
|
||||
#include "Instance.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <set>
|
||||
#ifdef __clang__
|
||||
/* ignore -Wcomma due to strange code in boost/array.hpp (in Boost
|
||||
1.72) */
|
||||
#pragma GCC diagnostic ignored "-Wcomma"
|
||||
#endif
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
#define MOUNT_STATE_BEGIN "mount_begin"
|
||||
#define MOUNT_STATE_END "mount_end"
|
||||
#define MOUNT_STATE_STORAGE_URI "uri: "
|
||||
|
||||
@@ -402,7 +402,7 @@ private:
|
||||
break;
|
||||
|
||||
case State::HREF:
|
||||
response.href.assign(s, len);
|
||||
response.href.append(s, len);
|
||||
break;
|
||||
|
||||
case State::STATUS:
|
||||
@@ -482,7 +482,7 @@ class HttpListDirectoryOperation final : public PropfindOperation {
|
||||
public:
|
||||
HttpListDirectoryOperation(CurlGlobal &curl, const char *uri)
|
||||
:PropfindOperation(curl, uri, 1),
|
||||
base_path(UriPathOrSlash(uri)) {}
|
||||
base_path(CurlUnescape(GetEasy(), UriPathOrSlash(uri))) {}
|
||||
|
||||
std::unique_ptr<StorageDirectoryReader> Perform() {
|
||||
DeferStart();
|
||||
@@ -507,8 +507,7 @@ private:
|
||||
|
||||
/* kludge: ignoring case in this comparison to avoid
|
||||
false negatives if the web server uses a different
|
||||
case in hex digits in escaped characters; TODO:
|
||||
implement properly */
|
||||
case */
|
||||
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
|
||||
if (path == nullptr || *path == 0)
|
||||
return nullptr;
|
||||
@@ -531,11 +530,12 @@ protected:
|
||||
if (r.status != 200)
|
||||
return;
|
||||
|
||||
const auto escaped_name = HrefToEscapedName(r.href.c_str());
|
||||
if (escaped_name.IsNull())
|
||||
std::string href = CurlUnescape(GetEasy(), r.href.c_str());
|
||||
const auto name = HrefToEscapedName(href.c_str());
|
||||
if (name.IsNull())
|
||||
return;
|
||||
|
||||
entries.emplace_front(CurlUnescape(GetEasy(), escaped_name));
|
||||
entries.emplace_front(std::string(name.data, name.size));
|
||||
|
||||
auto &info = entries.front().info;
|
||||
info = StorageFileInfo(r.collection
|
||||
|
||||
@@ -45,6 +45,10 @@ ApplyTagFallback(TagType type, F &&f) noexcept
|
||||
"AlbumArtist"/"ArtistSort" was found */
|
||||
return f(TAG_ARTIST);
|
||||
|
||||
if (type == TAG_ALBUM_SORT)
|
||||
/* fall back to "Album" if no "AlbumSort" was found */
|
||||
return f(TAG_ALBUM);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ SetThreadIdlePriority() noexcept
|
||||
#elif defined(_WIN32)
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
SetThreadRealtime()
|
||||
@@ -111,4 +111,4 @@ SetThreadRealtime()
|
||||
if (linux_sched_setscheduler(0, policy, &sched_param) < 0)
|
||||
throw MakeErrno("sched_setscheduler failed");
|
||||
#endif // __linux__
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,11 +30,16 @@
|
||||
#ifndef MATH_HXX
|
||||
#define MATH_HXX
|
||||
|
||||
#if (defined(__GLIBCPP__) || defined(__GLIBCXX__)) && !defined(_GLIBCXX_USE_C99_MATH)
|
||||
#include <cmath>
|
||||
|
||||
/*
|
||||
* C99 math can be optionally omitted with gcc's libstdc++.
|
||||
* Use boost if unavailable.
|
||||
*/
|
||||
#if (defined(__GLIBCPP__) || defined(__GLIBCXX__)) && !defined(_GLIBCXX_USE_C99_MATH_TR1)
|
||||
#include <boost/math/special_functions/round.hpp>
|
||||
using boost::math::lround;
|
||||
#else
|
||||
#include <cmath>
|
||||
using std::lround;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#ifndef STRING_FORMAT_HXX
|
||||
#define STRING_FORMAT_HXX
|
||||
|
||||
#include "StringBuffer.hxx"
|
||||
#include "StringBuffer.hxx" // IWYU pragma: export
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -121,6 +121,6 @@ namespace TemplateString {
|
||||
|
||||
template<>
|
||||
struct Concat<> : Empty {};
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <wchar.h>
|
||||
#include <cwchar>
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static inline size_t
|
||||
|
||||
@@ -26,9 +26,7 @@ static unsigned
|
||||
FromAvahiWatchEvent(AvahiWatchEvent e)
|
||||
{
|
||||
return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) |
|
||||
(e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) |
|
||||
(e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) |
|
||||
(e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0);
|
||||
(e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0);
|
||||
}
|
||||
|
||||
static AvahiWatchEvent
|
||||
|
||||
@@ -96,6 +96,9 @@ thirdparty_libs = [
|
||||
zlib,
|
||||
libid3tag,
|
||||
liblame,
|
||||
libmodplug,
|
||||
wildmidi,
|
||||
gme,
|
||||
ffmpeg,
|
||||
curl,
|
||||
libexpat,
|
||||
|
||||
Reference in New Issue
Block a user