Compare commits
352 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9274bc15bc | ||
![]() |
751fff07fb | ||
![]() |
f7d1408a1a | ||
![]() |
e4e14ef6b0 | ||
![]() |
005e691339 | ||
![]() |
61eff1cddf | ||
![]() |
c26703b7e6 | ||
![]() |
db27bb76e2 | ||
![]() |
7cfe929c36 | ||
![]() |
6c06244e83 | ||
![]() |
53448e4633 | ||
![]() |
21adc78713 | ||
![]() |
0340b01392 | ||
![]() |
94aed92e9a | ||
![]() |
6b9966e969 | ||
![]() |
4bc5333995 | ||
![]() |
ff58b8d255 | ||
![]() |
3f3f0af543 | ||
![]() |
850d208b7b | ||
![]() |
da563940b4 | ||
![]() |
282859a62a | ||
![]() |
fbeb5eefdc | ||
![]() |
85bada0505 | ||
![]() |
cf96135125 | ||
![]() |
1ff97783ea | ||
![]() |
2bc42c6445 | ||
![]() |
49372a222f | ||
![]() |
9127afbf3f | ||
![]() |
f2caac595a | ||
![]() |
14d3a7ae83 | ||
![]() |
f37ab5482b | ||
![]() |
ef38dbe5bf | ||
![]() |
54a5491b86 | ||
![]() |
aff070bcbb | ||
![]() |
5af2632d4f | ||
![]() |
44a31357f4 | ||
![]() |
29f78b18b1 | ||
![]() |
147872fe97 | ||
![]() |
38edb58054 | ||
![]() |
98afae2520 | ||
![]() |
ddc85c620f | ||
![]() |
12bc625fe1 | ||
![]() |
6b407356b9 | ||
![]() |
a4e0b52468 | ||
![]() |
98efb4f6d5 | ||
![]() |
36edb4886c | ||
![]() |
76290f786d | ||
![]() |
c6299c26b5 | ||
![]() |
fb5f9baf9c | ||
![]() |
dee591d970 | ||
![]() |
a5cc13b0c5 | ||
![]() |
aaf588aeaa | ||
![]() |
533a3def9f | ||
![]() |
fcf487f4e0 | ||
![]() |
906972973e | ||
![]() |
116edf5fce | ||
![]() |
8581013911 | ||
![]() |
b1e073bacd | ||
![]() |
501e48daba | ||
![]() |
643ecd1edd | ||
![]() |
7393e1cba1 | ||
![]() |
ceee47fda8 | ||
![]() |
6f3c0d0a60 | ||
![]() |
466625f7ad | ||
![]() |
b8259e604a | ||
![]() |
86e2075c63 | ||
![]() |
30900b2fe2 | ||
![]() |
fd7ae7ea4c | ||
![]() |
60d5bf0240 | ||
![]() |
41cdc4e14b | ||
![]() |
87dfca0477 | ||
![]() |
e1ee8e7812 | ||
![]() |
63406efcd8 | ||
![]() |
d5c132fca0 | ||
![]() |
5f082a2739 | ||
![]() |
7d6a762845 | ||
![]() |
8dcb1f805d | ||
![]() |
a8b9e5b9b9 | ||
![]() |
04f928e2b0 | ||
![]() |
c7a803c922 | ||
![]() |
ab197b6d43 | ||
![]() |
16b0e53a36 | ||
![]() |
bc14a6038e | ||
![]() |
626329a1cc | ||
![]() |
8bf250c228 | ||
![]() |
62127bbb12 | ||
![]() |
786ac87b76 | ||
![]() |
c76f4ac89b | ||
![]() |
d495ec71a8 | ||
![]() |
b763852f57 | ||
![]() |
6522d2f722 | ||
![]() |
ac61fd1d78 | ||
![]() |
c44d1566fa | ||
![]() |
80dc7c2f74 | ||
![]() |
7b94f0e36b | ||
![]() |
504e8d564a | ||
![]() |
ac395429c3 | ||
![]() |
388768b3a6 | ||
![]() |
5c4169e64e | ||
![]() |
d40e9de2d2 | ||
![]() |
1e54297be8 | ||
![]() |
44b200240f | ||
![]() |
a2340c313f | ||
![]() |
37b07a5e7c | ||
![]() |
73013a3c04 | ||
![]() |
e8099f01b5 | ||
![]() |
672bdd3a56 | ||
![]() |
c2c2c29658 | ||
![]() |
c745e14f47 | ||
![]() |
e8f08cda53 | ||
![]() |
8266ab5588 | ||
![]() |
ea552208fc | ||
![]() |
e86015a72a | ||
![]() |
cf7ec2c9d3 | ||
![]() |
dadd3ca671 | ||
![]() |
79535212c8 | ||
![]() |
ef5f96a193 | ||
![]() |
418f71ec0f | ||
![]() |
0ebeaa9ac2 | ||
![]() |
25cd47b8dc | ||
![]() |
cd48d981b5 | ||
![]() |
774d26b982 | ||
![]() |
f3e683bd6f | ||
![]() |
50ce0c0d9d | ||
![]() |
5b80711d75 | ||
![]() |
666e456551 | ||
![]() |
31794ac376 | ||
![]() |
2141fdf06e | ||
![]() |
3f3e0739c4 | ||
![]() |
ebed7e2147 | ||
![]() |
53f5d4c710 | ||
![]() |
139a4054c5 | ||
![]() |
a4de96508d | ||
![]() |
a7582aaf15 | ||
![]() |
c5c1c64a81 | ||
![]() |
992c52ce7f | ||
![]() |
026aef7465 | ||
![]() |
b53a23b51b | ||
![]() |
2aad015392 | ||
![]() |
986ec877b0 | ||
![]() |
c43ea74b30 | ||
![]() |
79981f3cda | ||
![]() |
c2940a8385 | ||
![]() |
bede564618 | ||
![]() |
e0ca4b865a | ||
![]() |
31c206bf80 | ||
![]() |
9187a08106 | ||
![]() |
3859a50466 | ||
![]() |
927071e085 | ||
![]() |
6ba918b203 | ||
![]() |
e8b70dbca4 | ||
![]() |
0f8d223c7f | ||
![]() |
19a2885fd5 | ||
![]() |
b8a094470b | ||
![]() |
2988bb77e8 | ||
![]() |
738317bf34 | ||
![]() |
e46fbd0780 | ||
![]() |
56b74ad990 | ||
![]() |
6de92bb42b | ||
![]() |
c801936e53 | ||
![]() |
817656504d | ||
![]() |
6f00f97b66 | ||
![]() |
5acb978f8f | ||
![]() |
975a4ae871 | ||
![]() |
56aaf3c73e | ||
![]() |
12fd1cad0c | ||
![]() |
e573cbf032 | ||
![]() |
dead461542 | ||
![]() |
3d5da1ac73 | ||
![]() |
ec408ca6a6 | ||
![]() |
ea66cdd6a5 | ||
![]() |
f762e8034f | ||
![]() |
bb1e369f30 | ||
![]() |
8376578921 | ||
![]() |
ed2354cd9d | ||
![]() |
386688b87a | ||
![]() |
38d56dddf1 | ||
![]() |
e8975942ec | ||
![]() |
3ca80a7336 | ||
![]() |
d029dae7ad | ||
![]() |
9e058732ee | ||
![]() |
cad5d11261 | ||
![]() |
fcaedec2ab | ||
![]() |
ead9d59e88 | ||
![]() |
34b8a17ccd | ||
![]() |
a53d081c39 | ||
![]() |
823134e4ba | ||
![]() |
272167b4fc | ||
![]() |
92f09bba94 | ||
![]() |
1f50bdb230 | ||
![]() |
2eef4e6716 | ||
![]() |
d989dbfec4 | ||
![]() |
ca9fcec364 | ||
![]() |
354104f9a9 | ||
![]() |
8649ea3d6f | ||
![]() |
752ff12c37 | ||
![]() |
4bb89b1755 | ||
![]() |
0ef553d30e | ||
![]() |
43a62aef07 | ||
![]() |
ed4d0aa909 | ||
![]() |
023ce4e720 | ||
![]() |
368d9359dd | ||
![]() |
d98c19d561 | ||
![]() |
cab77e35e0 | ||
![]() |
e3e90b4b93 | ||
![]() |
f8c69893e1 | ||
![]() |
49678a0893 | ||
![]() |
d667b5b48c | ||
![]() |
9cba55b39c | ||
![]() |
c2cbb7b8ce | ||
![]() |
8217d75ca1 | ||
![]() |
1ca70d9759 | ||
![]() |
4303aaa9b8 | ||
![]() |
7b56bae289 | ||
![]() |
4183416b3e | ||
![]() |
a60dee57ce | ||
![]() |
5724656acb | ||
![]() |
329f9cd9fe | ||
![]() |
fbdb8b406e | ||
![]() |
85d0bbd957 | ||
![]() |
414f00d6ae | ||
![]() |
17b0add058 | ||
![]() |
c68ed40661 | ||
![]() |
ff624075a8 | ||
![]() |
08db28469d | ||
![]() |
a20b326807 | ||
![]() |
4db1b1b250 | ||
![]() |
ff6b263b48 | ||
![]() |
c0bf052fa9 | ||
![]() |
5419cff925 | ||
![]() |
eee10ad2ed | ||
![]() |
98472a8104 | ||
![]() |
d094c168aa | ||
![]() |
4b18460bc6 | ||
![]() |
412c0a965c | ||
![]() |
2becf79223 | ||
![]() |
43ec96d4a0 | ||
![]() |
3d1d779da7 | ||
![]() |
c88056ba83 | ||
![]() |
e769751221 | ||
![]() |
49b9a90c3f | ||
![]() |
64d141f71e | ||
![]() |
c488d3123f | ||
![]() |
967af60327 | ||
![]() |
f1ef9f9d31 | ||
![]() |
dfaf08743c | ||
![]() |
d9552d8a6d | ||
![]() |
7586a8ab2c | ||
![]() |
e1a942250b | ||
![]() |
72be0185de | ||
![]() |
7e4cbce06b | ||
![]() |
177d62f431 | ||
![]() |
5a11e03725 | ||
![]() |
75d068b7cd | ||
![]() |
1208503888 | ||
![]() |
de90d401d2 | ||
![]() |
396defaea9 | ||
![]() |
18f350cd04 | ||
![]() |
478180ebe4 | ||
![]() |
4a3059f509 | ||
![]() |
78728138a0 | ||
![]() |
63fc98591d | ||
![]() |
53def9a682 | ||
![]() |
323231d1dd | ||
![]() |
714011c81e | ||
![]() |
952ff4207b | ||
![]() |
150b16ec2c | ||
![]() |
c98bc4a243 | ||
![]() |
014f8cd693 | ||
![]() |
aea37e46e3 | ||
![]() |
31ab78ae8e | ||
![]() |
f82e1453e4 | ||
![]() |
a2b77c8813 | ||
![]() |
18add29472 | ||
![]() |
b111a8fe8d | ||
![]() |
3b23cf0258 | ||
![]() |
28e864e096 | ||
![]() |
1de19b921a | ||
![]() |
ff162b5a03 | ||
![]() |
d8e4705dd4 | ||
![]() |
338e1f5926 | ||
![]() |
a7fdfa08e1 | ||
![]() |
9703a401c5 | ||
![]() |
753a2aa462 | ||
![]() |
10990a0684 | ||
![]() |
91254e9211 | ||
![]() |
0f79287b04 | ||
![]() |
f2fac77d8c | ||
![]() |
81b7373637 | ||
![]() |
fa67c2548a | ||
![]() |
ea80587ddb | ||
![]() |
828f5f8384 | ||
![]() |
1295a1272a | ||
![]() |
66646d9276 | ||
![]() |
d0497dba92 | ||
![]() |
42914e8227 | ||
![]() |
59b49b7881 | ||
![]() |
5620f16330 | ||
![]() |
be024d4ad7 | ||
![]() |
75c740fe2b | ||
![]() |
6c8d86bb90 | ||
![]() |
b253a6b71e | ||
![]() |
ca7b4df812 | ||
![]() |
bc8dd57236 | ||
![]() |
f4f461b8bb | ||
![]() |
cbb9b6957f | ||
![]() |
f6b56c9317 | ||
![]() |
3717fb6c8d | ||
![]() |
f6abbc01bd | ||
![]() |
57a71c157d | ||
![]() |
cc76aeb7bb | ||
![]() |
811cabf8a9 | ||
![]() |
bf8d2f93d2 | ||
![]() |
07d8259ad6 | ||
![]() |
a00d412008 | ||
![]() |
5fb39658f1 | ||
![]() |
b0703b92c3 | ||
![]() |
dd9fd3d8a7 | ||
![]() |
cf0c59864f | ||
![]() |
4c0404c70d | ||
![]() |
573a413ee1 | ||
![]() |
f633e6ca49 | ||
![]() |
07b06d76be | ||
![]() |
856fe2da15 | ||
![]() |
f82aae65cd | ||
![]() |
3fbd11a104 | ||
![]() |
58a99f1907 | ||
![]() |
cf86dfd317 | ||
![]() |
a057b4f6d8 | ||
![]() |
62b03cfddf | ||
![]() |
18b827b979 | ||
![]() |
0a379fc514 | ||
![]() |
445c11b8d9 | ||
![]() |
8d290ad509 | ||
![]() |
b90c48b50f | ||
![]() |
d19e7db09e | ||
![]() |
9939904b02 | ||
![]() |
ca23b15f5c | ||
![]() |
ffa676f577 | ||
![]() |
6d023c4df3 | ||
![]() |
b31bd37a30 | ||
![]() |
78faee8c7c | ||
![]() |
40e2a703d0 | ||
![]() |
b01edcb9bc | ||
![]() |
f7fffc9be8 | ||
![]() |
50e8634097 | ||
![]() |
e3994e517e | ||
![]() |
2bb7785189 | ||
![]() |
90c8408111 | ||
![]() |
64786ec12a | ||
![]() |
b3c82f8886 | ||
![]() |
063259dc52 |
.travis.ymlAUTHORSINSTALLMakefile.amNEWSREADME.md
android
configure.acdoc
python/build
src
AudioFormat.cxxAudioFormat.hxxCommandLine.cxxDetachedSong.hxxIOThread.cxxIOThread.hxxLocateUri.cxxLocateUri.hxxLogBackend.cxxLogInit.cxxLogLevel.hxxMain.cxxMain.hxxMixRampInfo.hxxMusicBuffer.hxxMusicChunk.hxxMusicPipe.cxxMusicPipe.hxxPlaylistFile.cxxPlaylistSave.cxxSongFilter.cxxSongFilter.hxxSongSave.cxxStateFile.cxxStateFile.hxxStats.cxxTimePrint.cxxls.cxx
android
archive
client
command
config
db
Count.cxxDatabasePrint.cxxDatabasePrint.hxxHelpers.cxxInterface.hxxLightDirectory.hxxPlaylistInfo.hxxSelection.cxxUniqueTags.cxxUniqueTags.hxx
plugins
ProxyDatabasePlugin.cxx
simple
Directory.cxxDirectory.hxxDirectorySave.cxxMount.cxxMount.hxxSimpleDatabasePlugin.cxxSimpleDatabasePlugin.hxx
upnp
update
decoder
Bridge.cxxBridge.hxxClient.hxxDecoderBuffer.hxxDecoderControl.cxxDecoderControl.hxxDecoderThread.cxx
plugins
DsdLib.cxxDsdiffDecoderPlugin.cxxDsfDecoderPlugin.cxxFfmpegDecoderPlugin.cxxFfmpegMetaData.cxxFlacCommon.cxxFlacCommon.hxxFlacDecoderPlugin.cxxFlacIOHandle.cxxFlacMetadata.hxxFluidsynthDecoderPlugin.cxxGmeDecoderPlugin.cxxOpusDecoderPlugin.cxxOpusTags.cxxSidplayDecoderPlugin.cxxVorbisDecoderPlugin.cxx
encoder
event
Loop.cxxLoop.hxxMultiSocketMonitor.cxxMultiSocketMonitor.hxxServerSocket.cxxSignalMonitor.cxxSignalMonitor.hxxSocketMonitor.cxxSocketMonitor.hxx
filter
fs
AllocatedPath.cxxAllocatedPath.hxxCharset.cxxCharset.hxxCheckFile.cxxDirectoryReader.cxxDirectoryReader.hxxFileInfo.hxxFileSystem.cxxFileSystem.hxxGlob.hxxLimits.hxxPath.hxxStandardDirectory.cxxStandardDirectory.hxxTraits.cxxTraits.hxx
io
input
AsyncInputStream.cxxAsyncInputStream.hxxError.cxxError.hxxInputPlugin.hxxInputStream.cxxInputStream.hxxOffset.hxxOpen.cxxThreadInputStream.cxxThreadInputStream.hxx
plugins
java
lib
curl
ffmpeg
icu
nfs
upnp
mixer
neighbor
net
AllocatedSocketAddress.hxxInit.hxxResolver.cxxSocketAddress.hxxSocketError.cxxSocketError.hxxStaticSocketAddress.hxxToString.cxx
output
pcm
ConfiguredResampler.cxxOrder.cxxPcmDop.cxxPcmExport.cxxResampler.hxxSampleFormat.hxxSoxrResampler.cxxSoxrResampler.hxx
player
playlist
protocol
queue
storage
system
ByteOrder.hxxClock.cxxClock.hxxEPollFD.cxxError.hxxEventPipe.cxxFatalError.cxxFatalError.hxxFileDescriptor.cxxFileDescriptor.hxxfd_util.cfd_util.h
tag
Aiff.cxxFallback.hxxFormat.cxxId3MusicBrainz.cxxId3MusicBrainz.hxxMixRamp.cxxReplayGain.cxxSet.cxxSet.hxxTagBuilder.cxxTagBuilder.hxxTagHandler.cxxTagId3.cxxVisitFallback.hxx
thread
unix
util
ASCII.hxxConstBuffer.hxxFormatString.cxxHugeAllocator.cxxHugeAllocator.hxxNumberParser.hxxRefCount.hxxStringAPI.hxxStringFormat.hxxStringView.cxxStringView.hxxTimeParser.cxxUriUtil.cxxWStringAPI.hxx
win32
test
FakeDecoderAPI.cxxFakeDecoderAPI.hxxNullMixerListener.hxxShutdownHandler.cxxShutdownHandler.hxxread_mixer.cxxrun_output.cxxrun_resolver.cxxrun_storage.cxxtest_byte_reverse.cxxtest_protocol.cxx
win32
36
.travis.yml
Normal file
36
.travis.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
dist: trusty
|
||||
language: cpp
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcppunit-dev
|
||||
- libboost-dev
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
env:
|
||||
global:
|
||||
- MAKEFLAGS="-j2"
|
||||
|
||||
before_install:
|
||||
# C++14
|
||||
- test "$TRAVIS_OS_NAME" != "linux" || sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
- test "$TRAVIS_OS_NAME" != "linux" || sudo apt-get update -qq
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew update
|
||||
|
||||
install:
|
||||
# C++14
|
||||
- test "$TRAVIS_OS_NAME" != "linux" || sudo apt-get install -qq g++-5
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install cppunit
|
||||
|
||||
script:
|
||||
- OPTIONS="--enable-test"
|
||||
- test "$TRAVIS_OS_NAME" != "linux" || export CC=gcc-5 CXX=g++-5
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || OPTIONS="$OPTIONS --enable-osx"
|
||||
- ./autogen.sh
|
||||
- ./configure --disable-silent-rules --disable-dependency-tracking $OPTIONS
|
||||
- make
|
||||
- make check
|
6
AUTHORS
6
AUTHORS
@@ -30,3 +30,9 @@ The following people have contributed code to MPD:
|
||||
Jurgen Kramer <gtmkramer@xs4all.nl>
|
||||
Jean-Francois Dockes <jf@dockes.org>
|
||||
Yue Wang <yuleopen@gmail.com>
|
||||
Matthew Leon Grinshpun <ml@matthewleon.com>
|
||||
Dimitris Papastamos <sin@2f30.org>
|
||||
Florian Schlichting <fsfs@debian.org>
|
||||
François Revol <revol@free.fr>
|
||||
Jacob Vosmaer <contact@jacobvosmaer.nl>
|
||||
Thomas Guillem <thomas@gllm.fr>
|
||||
|
206
INSTALL
206
INSTALL
@@ -1,206 +0,0 @@
|
||||
Music Player Daemon (MPD) - INSTALL
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This document is a very small amount of documentation about what is needed to
|
||||
install MPD. If more information is desired, read the user manual:
|
||||
|
||||
http://www.musicpd.org/doc/user/
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
gcc 4.7 or later - http://gcc.gnu.org/
|
||||
clang 3.2 or later - http://clang.llvm.org/
|
||||
Any other C++11 compliant compiler should also work.
|
||||
|
||||
Boost 1.46 - http://www.boost.org/
|
||||
|
||||
|
||||
Optional Output Dependencies
|
||||
----------------------------
|
||||
|
||||
You will need at least one of these to compile MPD.
|
||||
|
||||
Most of these are available as packages on major distributions. Be sure to
|
||||
install both the library package as well as the development package.
|
||||
|
||||
AO - http://www.xiph.org/ao/
|
||||
A portable library that abstracts many audio output types as one API. Should
|
||||
be used only if there is no native plugin available or if the native plugin
|
||||
doesn't work. You will need libao.
|
||||
|
||||
ALSA - http://www.alsa-project.org/
|
||||
The Advanced Linux Sound Architecture. Recommended audio output if you use
|
||||
Linux. You will need libasound.
|
||||
|
||||
FIFO
|
||||
This is a mostly undocumented, developer plugin to transmit raw data.
|
||||
|
||||
OSS - http://www.opensound.com
|
||||
Open Sound System.
|
||||
|
||||
PulseAudio - http://www.pulseaudio.org/
|
||||
An advanced sound daemon. You will need libpulse.
|
||||
|
||||
JACK - http://www.jackaudio.org/
|
||||
A low-latency sound daemon.
|
||||
|
||||
libshout - http://www.icecast.org/
|
||||
For streaming to an Icecast or Shoutcast server.
|
||||
You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
|
||||
|
||||
OpenAL - http://kcat.strangesoft.net/openal.html
|
||||
Open Audio Library
|
||||
|
||||
|
||||
Optional Input Dependencies
|
||||
---------------------------
|
||||
|
||||
You will need at least one of these to compile MPD.
|
||||
|
||||
Most of these are available as packages on major distributions. Be sure to
|
||||
install both the library package as well as the development package.
|
||||
|
||||
MAD - http://www.underbit.com/products/mad/
|
||||
For MP3 support. You will need libmad, and optionally libid3tag if you want
|
||||
ID3 tag support.
|
||||
|
||||
libmpg123 - http://www.mpg123.de/
|
||||
Alternative for MP3 support.
|
||||
|
||||
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
|
||||
For Ogg Vorbis support. You will need libogg and libvorbis.
|
||||
|
||||
libopus - http://www.opus-codec.org/
|
||||
Opus codec support
|
||||
|
||||
FLAC - http://flac.sourceforge.net/
|
||||
For FLAC support. You will need version 1.2 or higher of libFLAC.
|
||||
|
||||
Audio File - http://www.68k.org/~michael/audiofile/
|
||||
For WAVE, AIFF, and AU support. You will need libaudiofile.
|
||||
|
||||
FAAD2 - http://www.audiocoding.com/
|
||||
For MP4/AAC support.
|
||||
|
||||
libmpcdec - http://www.musepack.net/
|
||||
For Musepack support.
|
||||
|
||||
MikMod - http://mikmod.raphnet.net/
|
||||
For MOD support. You will need libmikmod.
|
||||
|
||||
libavcodec, libavformat (ffmpeg or libav) - http://ffmpeg.mplayerhq.hu/ http://libav.org/
|
||||
Multi-codec library.
|
||||
|
||||
libsidplay2 - http://sidplay2.sourceforge.net/
|
||||
For C64 SID support.
|
||||
|
||||
libfluidsynth - http://fluidsynth.resonance.org/
|
||||
For MIDI support.
|
||||
|
||||
libwildmidi 0.2.3 - http://wildmidi.sourceforge.net/
|
||||
For MIDI support.
|
||||
|
||||
libsndfile - http://www.mega-nerd.com/libsndfile/
|
||||
WAVE, AIFF, and many others.
|
||||
|
||||
libwavpack - http://www.wavpack.com/
|
||||
For WavPack playback.
|
||||
|
||||
libadplug - http://adplug.sourceforge.net/
|
||||
For AdLib playback.
|
||||
|
||||
|
||||
Optional Miscellaneous Dependencies
|
||||
-----------------------------------
|
||||
|
||||
Avahi - http://www.avahi.org/
|
||||
For Zeroconf support.
|
||||
|
||||
libsamplerate - http://www.mega-nerd.com/SRC/
|
||||
For advanced samplerate conversions.
|
||||
|
||||
libcurl - http://curl.haxx.se/
|
||||
For playing HTTP streams.
|
||||
|
||||
libmms - https://launchpad.net/libmms
|
||||
For playing MMS streams.
|
||||
|
||||
SQLite - http://www.sqlite.org/
|
||||
For the sticker database.
|
||||
|
||||
libcdio - http://www.gnu.org/software/libcdio/
|
||||
For playing audio CDs.
|
||||
|
||||
libsystemd-daemon - http://freedesktop.org/wiki/Software/systemd/
|
||||
For systemd activation.
|
||||
|
||||
|
||||
pkg-config
|
||||
----------
|
||||
|
||||
MPD uses pkg-config to locate most external libraries. If you do not
|
||||
have pkg-config, or if your version of the library does not ship the
|
||||
".pc" file, you have to provide the library's build options in
|
||||
environment variables. These variables are documented in "./configure
|
||||
--help". Example:
|
||||
|
||||
FLAC_CFLAGS=-I/usr/include/FLAC FLAC_LIBS=-lFLAC ./configure
|
||||
|
||||
|
||||
Download
|
||||
--------
|
||||
|
||||
Get the latest release from of MPD from <http://www.musicpd.org/>.
|
||||
|
||||
Compile
|
||||
-------
|
||||
|
||||
1) unpack the archive
|
||||
|
||||
$ tar xf mpd-x.x.x.tar.xz
|
||||
|
||||
2) change to directory created
|
||||
|
||||
$ cd mpd-x.x.x
|
||||
|
||||
3) Run configure script (this will determine what dependencies you have)
|
||||
|
||||
$ ./configure
|
||||
|
||||
4) Compile
|
||||
|
||||
$ make
|
||||
|
||||
Install (Optional)
|
||||
-------
|
||||
|
||||
(as root)
|
||||
$ make install
|
||||
|
||||
Run
|
||||
---
|
||||
|
||||
1) run mpd:
|
||||
|
||||
$ mpd <config file>
|
||||
|
||||
First default is $XDG_CONFIG_HOME/mpd/mpd.conf then ~/.mpdconf then
|
||||
~/.mpd/mpd.conf then /etc/mpd.conf. If neither of these exist a mpd
|
||||
configuration file must be specified at runtime.
|
||||
|
||||
A sample config file is included with the source of MPD, mpdconf.example.
|
||||
|
||||
The first time MPD is run it will attempt to discover all music in your
|
||||
music root, recursively. This can be affected by the symbolic link
|
||||
options specified in the example mpd.conf.
|
||||
|
||||
Using MPD
|
||||
---------
|
||||
|
||||
You can download many different interfaces for MPD at
|
||||
|
||||
http://www.musicpd.org/clients/
|
154
Makefile.am
154
Makefile.am
@@ -61,8 +61,8 @@ src_mpd_LDADD = \
|
||||
libnet.a \
|
||||
$(FS_LIBS) \
|
||||
libsystem.a \
|
||||
libutil.a \
|
||||
$(ICU_LDADD) \
|
||||
libutil.a \
|
||||
$(SYSTEMD_DAEMON_LIBS)
|
||||
|
||||
src_mpd_SOURCES = \
|
||||
@@ -234,6 +234,7 @@ libmpd_a_SOURCES += \
|
||||
endif
|
||||
|
||||
CURL_SOURCES = \
|
||||
src/lib/curl/Error.hxx \
|
||||
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
|
||||
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
|
||||
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
|
||||
@@ -243,6 +244,7 @@ CURL_SOURCES = \
|
||||
src/lib/curl/Slist.hxx
|
||||
|
||||
UPNP_SOURCES = \
|
||||
src/lib/upnp/Compat.hxx \
|
||||
src/lib/upnp/Init.cxx src/lib/upnp/Init.hxx \
|
||||
src/lib/upnp/ClientInit.cxx src/lib/upnp/ClientInit.hxx \
|
||||
src/lib/upnp/Device.cxx src/lib/upnp/Device.hxx \
|
||||
@@ -274,7 +276,8 @@ libjava_a_SOURCES = \
|
||||
noinst_LIBRARIES += libandroid.a
|
||||
libandroid_a_SOURCES = \
|
||||
src/android/Context.cxx src/android/Context.hxx \
|
||||
src/android/Environment.cxx src/android/Environment.hxx
|
||||
src/android/Environment.cxx src/android/Environment.hxx \
|
||||
src/android/LogListener.cxx src/android/LogListener.hxx
|
||||
libandroid_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
|
||||
|
||||
noinst_LIBRARIES += libmain.a
|
||||
@@ -284,56 +287,108 @@ libmain_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
|
||||
|
||||
src_mpd_LDADD += libandroid.a libjava.a
|
||||
|
||||
all-local: android/build/bin/$(APK_NAME)-debug.apk
|
||||
all-local: android/build/$(APK_NAME)-debug.apk
|
||||
clean-local:
|
||||
rm -rf android/build
|
||||
|
||||
libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
|
||||
|
||||
android/build/build.xml: android/AndroidManifest.xml
|
||||
rm -rf android/build
|
||||
mkdir -p android/build/include android/build/res android/build/src/org
|
||||
ln -s $(abs_srcdir)/android/AndroidManifest.xml $(abs_srcdir)/android/custom_rules.xml android/build
|
||||
ln -s $(abs_srcdir)/android/src android/build/src/org/musicpd
|
||||
ln -s $(abs_srcdir)/android/res/values $(abs_srcdir)/android/res/layout android/build/res
|
||||
$(ANDROID_SDK)/tools/android update project --path android/build --target android-17 --name $(APK_NAME)
|
||||
ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
|
||||
ANDROID_SDK_PLATFORM = android-21
|
||||
|
||||
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml android/build/res/drawable/icon.png
|
||||
cd android/build && ant compile-jni-classes
|
||||
ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
|
||||
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
|
||||
|
||||
android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
|
||||
javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
|
||||
JAVAC = javac
|
||||
AIDL = $(ANDROID_BUILD_TOOLS_DIR)/aidl
|
||||
AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
|
||||
DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
|
||||
ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
||||
|
||||
ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
|
||||
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
|
||||
|
||||
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java Settings.java
|
||||
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
|
||||
|
||||
JAVA_CLASSFILES_DIR = android/build/classes
|
||||
|
||||
AIDL_FILES = $(wildcard $(srcdir)/android/src/*.aidl)
|
||||
AIDL_JAVA_FILES = $(patsubst $(srcdir)/android/src/%.aidl,android/build/src/org/musicpd/%.java,$(AIDL_FILES))
|
||||
|
||||
android/build/src/org/musicpd/IMain.java: android/build/src/org/musicpd/IMainCallback.java
|
||||
|
||||
$(AIDL_JAVA_FILES): android/build/src/org/musicpd/%.java: $(srcdir)/android/src/%.aidl
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||
$(AM_V_at)cp $< $(@D)/
|
||||
$(AM_V_at)$(AIDL) -Iandroid/build/src -oandroid/build/src $(patsubst %.java,%.aidl,$@)
|
||||
|
||||
$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
|
||||
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||
$(AM_V_at)cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
|
||||
|
||||
android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png android/build/res/drawable/notification_icon.png
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)$(MKDIR_P) android/build/gen
|
||||
$(AM_V_at)$(AAPT) package -f -m --auto-add-overlay \
|
||||
--custom-package org.musicpd \
|
||||
-M $(srcdir)/android/AndroidManifest.xml \
|
||||
-S android/build/res \
|
||||
-J android/build/gen \
|
||||
-I $(ANDROID_SDK_PLATFORM_DIR)/android.jar \
|
||||
-F android/build/resources.apk
|
||||
|
||||
# R.java is generated by aapt, when resources.apk is generated
|
||||
android/build/gen/org/musicpd/R.java: android/build/resources.apk
|
||||
|
||||
android/build/classes.dex: $(JAVA_SOURCE_PATHS) $(AIDL_JAVA_FILES) android/build/gen/org/musicpd/R.java
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
|
||||
$(AM_V_at)$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
|
||||
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
|
||||
-h android/build/include \
|
||||
-d $(JAVA_CLASSFILES_DIR) $^
|
||||
$(AM_V_at)$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
|
||||
|
||||
android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
|
||||
|
||||
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
|
||||
|
||||
android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
|
||||
mkdir -p $(@D)
|
||||
rm -f $@
|
||||
$(STRIP) -o $@ $<
|
||||
android/build/lib/$(ANDROID_ABI)/libmpd.so: libmpd.so
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||
$(AM_V_at)rm -f $@
|
||||
$(AM_V_at)$(STRIP) -o $@ $<
|
||||
|
||||
android/build/res/drawable/icon.png: mpd.svg
|
||||
mkdir -p $(@D)
|
||||
rsvg-convert --width=48 --height=48 $< -o $@
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||
$(AM_V_at)rsvg-convert --width=48 --height=48 $< -o $@
|
||||
|
||||
APK_DEPS = android/build/res/drawable/icon.png \
|
||||
android/build/libs/armeabi-v7a/libmpd.so \
|
||||
$(wildcard $(srcdir)/android/src/*.java) \
|
||||
android/build/build.xml
|
||||
android/build/res/drawable/notification_icon.png: android/build/res/drawable/icon.png
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)convert $< -colorspace Gray -gamma 2.2 $@
|
||||
|
||||
android/build/bin/$(APK_NAME)-debug.apk: $(APK_DEPS)
|
||||
cd android/build && ant nodeps debug
|
||||
.DELETE_ON_ERROR: android/build/unsigned.apk
|
||||
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/$(ANDROID_ABI)/libmpd.so
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)cp android/build/resources.apk $@
|
||||
$(AM_V_at)cd $(@D) && zip -q -r $(@F) classes.dex lib
|
||||
|
||||
android/build/bin/$(APK_NAME)-release-unsigned.apk: $(APK_DEPS)
|
||||
cd android/build && ant nodeps release
|
||||
android/build/$(APK_NAME)-debug.apk: android/build/unsigned.apk
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)jarsigner -keystore $(HOME)/.android/debug.keystore -storepass android -signedjar $@ $< androiddebugkey
|
||||
|
||||
android/build/bin/$(APK_NAME)-release-unaligned.apk: android/build/bin/$(APK_NAME)-release-unsigned.apk
|
||||
jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
|
||||
android/build/$(APK_NAME)-release-unaligned.apk: android/build/unsigned.apk
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
|
||||
|
||||
ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
|
||||
|
||||
android/build/bin/$(APK_NAME).apk: android/build/bin/$(APK_NAME)-release-unaligned.apk
|
||||
$(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)/zipalign -f 4 $< $@
|
||||
android/build/$(APK_NAME).apk: android/build/$(APK_NAME)-release-unaligned.apk
|
||||
$(AM_V_GEN)
|
||||
$(AM_V_at)$(ZIPALIGN) -f 4 $< $@
|
||||
|
||||
endif
|
||||
|
||||
@@ -416,6 +471,7 @@ libutil_a_SOURCES = \
|
||||
src/util/NumberParser.hxx \
|
||||
src/util/MimeType.cxx src/util/MimeType.hxx \
|
||||
src/util/StringBuffer.hxx \
|
||||
src/util/StringFormat.hxx \
|
||||
src/util/StringPointer.hxx \
|
||||
src/util/StringView.cxx src/util/StringView.hxx \
|
||||
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
|
||||
@@ -469,6 +525,7 @@ libthread_a_SOURCES = \
|
||||
|
||||
libnet_a_SOURCES = \
|
||||
src/net/Features.hxx \
|
||||
src/net/Init.hxx \
|
||||
src/net/ToString.cxx src/net/ToString.hxx \
|
||||
src/net/Resolver.cxx src/net/Resolver.hxx \
|
||||
src/net/StaticSocketAddress.cxx src/net/StaticSocketAddress.hxx \
|
||||
@@ -518,6 +575,8 @@ libevent_a_SOURCES = \
|
||||
# UTF-8 library
|
||||
|
||||
libicu_a_SOURCES = \
|
||||
src/lib/icu/CaseFold.cxx src/lib/icu/CaseFold.hxx \
|
||||
src/lib/icu/Compare.cxx src/lib/icu/Compare.hxx \
|
||||
src/lib/icu/Collate.cxx src/lib/icu/Collate.hxx \
|
||||
src/lib/icu/Converter.cxx src/lib/icu/Converter.hxx
|
||||
|
||||
@@ -681,6 +740,7 @@ NFS_SOURCES = \
|
||||
src/lib/nfs/Cancellable.hxx \
|
||||
src/lib/nfs/Lease.hxx \
|
||||
src/lib/nfs/Connection.cxx src/lib/nfs/Connection.hxx \
|
||||
src/lib/nfs/Error.cxx src/lib/nfs/Error.hxx \
|
||||
src/lib/nfs/Manager.cxx src/lib/nfs/Manager.hxx \
|
||||
src/lib/nfs/Glue.cxx src/lib/nfs/Glue.hxx \
|
||||
src/lib/nfs/Base.cxx src/lib/nfs/Base.hxx \
|
||||
@@ -699,9 +759,12 @@ libstorage_a_SOURCES = \
|
||||
src/storage/MemoryDirectoryReader.cxx src/storage/MemoryDirectoryReader.hxx \
|
||||
src/storage/Configured.cxx src/storage/Configured.hxx \
|
||||
src/storage/plugins/LocalStorage.cxx src/storage/plugins/LocalStorage.hxx \
|
||||
src/storage/StorageState.cxx src/storage/StorageState.hxx \
|
||||
src/storage/FileInfo.hxx
|
||||
|
||||
libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(CURL_CFLAGS) \
|
||||
$(EXPAT_CFLAGS) \
|
||||
$(NFS_CFLAGS) \
|
||||
$(SMBCLIENT_CFLAGS)
|
||||
|
||||
@@ -751,6 +814,7 @@ libneighbor_a_SOURCES = \
|
||||
src/neighbor/NeighborPlugin.hxx
|
||||
|
||||
libneighbor_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(UPNP_CFLAGS) \
|
||||
$(SMBCLIENT_CFLAGS)
|
||||
|
||||
if ENABLE_SMBCLIENT
|
||||
@@ -800,6 +864,8 @@ libdb_plugins_a_SOURCES = \
|
||||
src/db/plugins/simple/PrefixedLightSong.hxx \
|
||||
src/db/plugins/simple/SimpleDatabasePlugin.cxx \
|
||||
src/db/plugins/simple/SimpleDatabasePlugin.hxx
|
||||
libdb_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(UPNP_CFLAGS)
|
||||
|
||||
if ENABLE_LIBMPDCLIENT
|
||||
libdb_plugins_a_SOURCES += \
|
||||
@@ -911,18 +977,20 @@ libtag_a_SOURCES =\
|
||||
src/tag/TagItem.hxx \
|
||||
src/tag/TagHandler.cxx src/tag/TagHandler.hxx \
|
||||
src/tag/Mask.hxx \
|
||||
src/tag/Fallback.hxx \
|
||||
src/tag/VisitFallback.hxx \
|
||||
src/tag/Settings.cxx src/tag/Settings.hxx \
|
||||
src/tag/TagConfig.cxx src/tag/TagConfig.hxx \
|
||||
src/tag/TagNames.c \
|
||||
src/tag/TagString.cxx src/tag/TagString.hxx \
|
||||
src/tag/TagPool.cxx src/tag/TagPool.hxx \
|
||||
src/tag/TagTable.cxx src/tag/TagTable.hxx \
|
||||
src/tag/Set.cxx src/tag/Set.hxx \
|
||||
src/tag/Format.cxx src/tag/Format.hxx \
|
||||
src/tag/VorbisComment.cxx src/tag/VorbisComment.hxx \
|
||||
src/tag/ReplayGain.cxx src/tag/ReplayGain.hxx \
|
||||
src/tag/MixRamp.cxx src/tag/MixRamp.hxx \
|
||||
src/tag/Generic.cxx src/tag/Generic.hxx \
|
||||
src/tag/Id3MusicBrainz.cxx src/tag/Id3MusicBrainz.hxx \
|
||||
src/tag/ApeLoader.cxx src/tag/ApeLoader.hxx \
|
||||
src/tag/ApeReplayGain.cxx src/tag/ApeReplayGain.hxx \
|
||||
src/tag/ApeTag.cxx src/tag/ApeTag.hxx
|
||||
@@ -1256,7 +1324,7 @@ endif
|
||||
#
|
||||
|
||||
libinput_a_SOURCES = \
|
||||
src/input/Domain.cxx src/input/Domain.hxx \
|
||||
src/input/Error.cxx src/input/Error.hxx \
|
||||
src/input/Init.cxx src/input/Init.hxx \
|
||||
src/input/Registry.cxx src/input/Registry.hxx \
|
||||
src/input/Open.cxx \
|
||||
@@ -1489,6 +1557,8 @@ liboutput_plugins_a_SOURCES += \
|
||||
src/output/plugins/OSXOutputPlugin.cxx \
|
||||
src/output/plugins/OSXOutputPlugin.hxx
|
||||
endif
|
||||
libmixer_plugins_a_SOURCES += \
|
||||
src/mixer/plugins/OSXMixerPlugin.cxx
|
||||
|
||||
if ENABLE_PULSE
|
||||
liboutput_plugins_a_SOURCES += \
|
||||
@@ -1632,6 +1702,13 @@ FILTER_LIBS = \
|
||||
$(PCM_LIBS)
|
||||
|
||||
|
||||
#
|
||||
# Icon
|
||||
#
|
||||
|
||||
iconsdir = $(datadir)/icons/hicolor/scalable/apps
|
||||
icons_DATA = mpd.svg
|
||||
|
||||
#
|
||||
# systemd unit
|
||||
#
|
||||
@@ -2122,6 +2199,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
|
||||
libutil.a
|
||||
test_run_output_SOURCES = test/run_output.cxx \
|
||||
test/ScopeIOThread.hxx \
|
||||
test/NullMixerListener.hxx \
|
||||
src/Log.cxx src/LogBackend.cxx \
|
||||
src/IOThread.cxx \
|
||||
src/output/Domain.cxx \
|
||||
@@ -2145,6 +2223,7 @@ test_read_mixer_LDADD = \
|
||||
libsystem.a \
|
||||
libutil.a
|
||||
test_read_mixer_SOURCES = test/read_mixer.cxx \
|
||||
test/NullMixerListener.hxx \
|
||||
src/Log.cxx src/LogBackend.cxx \
|
||||
src/mixer/MixerControl.cxx \
|
||||
src/filter/FilterPlugin.cxx \
|
||||
@@ -2399,6 +2478,7 @@ endif
|
||||
#
|
||||
|
||||
EXTRA_DIST = $(doc_DATA) autogen.sh \
|
||||
mpd.svg \
|
||||
test/test_archive_bzip2.sh \
|
||||
test/test_archive_iso9660.sh \
|
||||
test/test_archive_zzip.sh \
|
||||
@@ -2406,6 +2486,7 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
|
||||
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
|
||||
$(wildcard $(srcdir)/doc/include/*.xml) \
|
||||
systemd/system/mpd.socket \
|
||||
$(wildcard $(srcdir)/python/build/*.py) \
|
||||
android/AndroidManifest.xml \
|
||||
android/build.py \
|
||||
android/custom_rules.xml \
|
||||
@@ -2413,5 +2494,6 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
|
||||
android/src/Bridge.java \
|
||||
android/src/Loader.java \
|
||||
android/src/Main.java \
|
||||
win32/build.py \
|
||||
win32/res/mpd.rc.in win32/res/mpd.ico \
|
||||
src/haiku/App_MusicPD
|
||||
|
170
NEWS
170
NEWS
@@ -1,3 +1,173 @@
|
||||
ver 0.20.22 (2018/10/23)
|
||||
* protocol
|
||||
- add tag fallbacks for AlbumArtistSort, ArtistSort
|
||||
- fix empty string filter on fallback tags
|
||||
- "count group ..." can print an empty group
|
||||
- fix broken command "list ... group"
|
||||
* storage
|
||||
- curl: URL-encode paths
|
||||
* decoder
|
||||
- fluidsynth: adapt to API change in version 2.0
|
||||
* Android
|
||||
- now runs as a service
|
||||
- add button to start/stop MPD
|
||||
- add option to auto-start on boot
|
||||
* work around clang bug leading to crash
|
||||
* install the SVG icon
|
||||
|
||||
ver 0.20.21 (2018/08/17)
|
||||
* database
|
||||
- proxy: add "password" setting
|
||||
- proxy: support tags "ArtistSort", "AlbumArtistSort", "AlbumSort"
|
||||
- simple: allow .mpdignore comments only at start of line
|
||||
* output
|
||||
- httpd: remove broken DLNA support code
|
||||
* playlist
|
||||
- cue: support file type declaration "FLAC" (non-standard)
|
||||
* URI schemes are case insensitive
|
||||
* Android, Windows
|
||||
- enable the "curl" storage plugin
|
||||
|
||||
ver 0.20.20 (2018/05/22)
|
||||
* protocol
|
||||
- fix "modified-since" filter regression
|
||||
* output
|
||||
- pulse: cork stream when paused due to "single" mode
|
||||
* decoder
|
||||
- dsdiff, dsf: support more MIME types
|
||||
- dsdiff, dsf: allow 4 MB ID3 tags
|
||||
- opus: support R128_ALBUM_GAIN tag
|
||||
* Android, Windows
|
||||
- enable the "proxy" database plugin
|
||||
|
||||
ver 0.20.19 (2018/04/26)
|
||||
* protocol
|
||||
- validate absolute seek time, reject negative values
|
||||
* database
|
||||
- proxy: fix "search already in progress" errors
|
||||
- proxy: implement "list ... group"
|
||||
* input
|
||||
- mms: fix lockup bug and a crash bug
|
||||
* decoder
|
||||
- ffmpeg: fix av_register_all() deprecation warning (FFmpeg 4.0)
|
||||
* player
|
||||
- fix spurious "Not seekable" error when switching radio streams
|
||||
* macOS: fix crash bug
|
||||
|
||||
ver 0.20.18 (2018/02/24)
|
||||
* input
|
||||
- curl: allow authentication methods other than "Basic"
|
||||
* decoder
|
||||
- flac: improve seeking precision
|
||||
* fix gapless CUE song transitions
|
||||
* Android, Windows
|
||||
- enable the NFS storage plugin
|
||||
|
||||
ver 0.20.17 (2018/02/11)
|
||||
* output
|
||||
- alsa: fix crash bug with 8 channels
|
||||
* mixer
|
||||
- alsa: fix rounding error at volume 0
|
||||
* fix real-time and idle scheduling with Musl
|
||||
* Android
|
||||
- fix compatibility with Android 4.0
|
||||
|
||||
ver 0.20.16 (2018/02/03)
|
||||
* output
|
||||
- pulse: fix crash during auto-detection
|
||||
* database
|
||||
- simple: fix search within mount points
|
||||
- upnp: enable IPv6
|
||||
* archive
|
||||
- iso9660: libcdio 2.0 compatibility
|
||||
* fix crash in debug build on Haiku and other operating systems
|
||||
|
||||
ver 0.20.15 (2018/01/05)
|
||||
* queue: fix crash after seek failure
|
||||
* resampler
|
||||
- soxr: clear internal state after manual song change
|
||||
* state file
|
||||
- make mount point restore errors non-fatal
|
||||
- fix crash when restoring mounts with incompatible database plugin
|
||||
* Android
|
||||
- build without Ant
|
||||
- fix for SIGSYS crash
|
||||
|
||||
ver 0.20.14 (2018/01/01)
|
||||
* database
|
||||
- simple: fix file corruption in the presence of mount points
|
||||
* archive
|
||||
- bz2: fix deadlock
|
||||
- reduce lock contention, fixing lots of xrun problems
|
||||
* fix Solaris build failure
|
||||
|
||||
ver 0.20.13 (2017/12/18)
|
||||
* output
|
||||
- osx: set up ring buffer to hold at least 100ms
|
||||
* mixer
|
||||
- alsa: fix rounding errors
|
||||
* database
|
||||
- simple: don't purge mount points on update/rescan
|
||||
- simple: fix "mount" bug caused by bad compiler optimization
|
||||
- simple: fix "lsinfo" into mount points
|
||||
- upnp: work around libupnp 1.6.24 API breakage
|
||||
* queue: fix spuriously misplaced prioritized songs
|
||||
* save and restore mountpoints within the state file
|
||||
* include Windows cross-build script in source tarball
|
||||
* fix Windows build failures
|
||||
|
||||
ver 0.20.12 (2017/11/25)
|
||||
* database
|
||||
- upnp: adapt to libupnp 1.8 API changes
|
||||
* input
|
||||
- cdio_paranoia, ffmpeg, file, smbclient: reduce lock contention,
|
||||
fixing lots of xrun problems
|
||||
- curl: fix seeking
|
||||
* decoder
|
||||
- ffmpeg: fix GCC 8 warning
|
||||
- vorbis: fix Tremor support
|
||||
* player
|
||||
- log message when decoder is too slow
|
||||
* encoder
|
||||
- vorbis: default to quality 3
|
||||
* output
|
||||
- fix hanging playback with soxr resampler
|
||||
- httpd: flush encoder after tag; fixes corrupt Vorbis stream
|
||||
|
||||
ver 0.20.11 (2017/10/18)
|
||||
* storage
|
||||
- curl: support Content-Type application/xml
|
||||
* decoder
|
||||
- ffmpeg: more reliable song duration
|
||||
- gme: fix track numbering
|
||||
* improve random song order when switching songs manually
|
||||
* fix case insensitive search without libicu
|
||||
* fix Unicode file names in playlists on Windows
|
||||
* fix endless loop when accessing malformed file names in ZIP files
|
||||
|
||||
ver 0.20.10 (2017/08/24)
|
||||
* decoder
|
||||
- ffmpeg: support MusicBrainz ID3v2 tags
|
||||
* tags
|
||||
- aiff: fix FORM chunk size endianess (is big-endian)
|
||||
* mixer
|
||||
- osx: add a mixer for OSX.
|
||||
* fix crash when resuming playback before decoder is ready
|
||||
* fix crash on Windows
|
||||
|
||||
ver 0.20.9 (2017/06/04)
|
||||
* decoder
|
||||
- ffmpeg: support *.adx
|
||||
* fix byte order detection on FreeBSD/aarch64
|
||||
* fix more random crashes when compiled with clang
|
||||
|
||||
ver 0.20.8 (2017/05/19)
|
||||
* output
|
||||
- osx: fix build failure due to missing "noexcept"
|
||||
* playlist
|
||||
- m3u: support MIME type `audio/mpegurl`
|
||||
* fix build failure with GCC 4.x
|
||||
|
||||
ver 0.20.7 (2017/05/15)
|
||||
* database
|
||||
- simple: fix false positive directory loop detection with NFS
|
||||
|
@@ -7,7 +7,8 @@ server's audio device. The daemon stores info about all available music,
|
||||
and this info can be easily searched and retrieved. Player control, info
|
||||
retrieval, and playlist management can all be managed remotely.
|
||||
|
||||
For basic installation information see the INSTALL file.
|
||||
For basic installation instructions
|
||||
[read the manual](https://www.musicpd.org/doc/user/install.html).
|
||||
|
||||
# Users
|
||||
|
||||
|
@@ -2,23 +2,32 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="13"
|
||||
android:versionName="0.19.9">
|
||||
android:versionCode="21"
|
||||
android:versionName="0.20.22">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name">
|
||||
<activity android:name=".Main"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleInstance">
|
||||
<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" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name">
|
||||
<activity android:name=".Settings"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<receiver android:name=".Receiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service android:name=".Main" android:process=":main"/>
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
@@ -3,13 +3,14 @@
|
||||
import os, os.path
|
||||
import sys, subprocess
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: build.py SDK_PATH NDK_PATH [configure_args...]", file=sys.stderr)
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: build.py SDK_PATH NDK_PATH ABI [configure_args...]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sdk_path = sys.argv[1]
|
||||
ndk_path = sys.argv[2]
|
||||
configure_args = sys.argv[3:]
|
||||
android_abi = sys.argv[3]
|
||||
configure_args = sys.argv[4:]
|
||||
|
||||
if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
|
||||
print("SDK not found in", ndk_path, file=sys.stderr)
|
||||
@@ -19,8 +20,36 @@ if not os.path.isdir(ndk_path):
|
||||
print("NDK not found in", ndk_path, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
android_abis = {
|
||||
'armeabi-v7a': {
|
||||
'arch': 'arm-linux-androideabi',
|
||||
'ndk_arch': 'arm',
|
||||
'toolchain_arch': 'arm-linux-androideabi',
|
||||
'llvm_triple': 'armv7-none-linux-androideabi',
|
||||
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
||||
},
|
||||
|
||||
'arm64-v8a': {
|
||||
'android_api_level': '21',
|
||||
'arch': 'aarch64-linux-android',
|
||||
'ndk_arch': 'arm64',
|
||||
'toolchain_arch': 'aarch64-linux-android',
|
||||
'llvm_triple': 'aarch64-none-linux-android',
|
||||
'cflags': '',
|
||||
},
|
||||
|
||||
'x86': {
|
||||
'arch': 'i686-linux-android',
|
||||
'ndk_arch': 'x86',
|
||||
'toolchain_arch': 'x86',
|
||||
'llvm_triple': 'i686-none-linux-android',
|
||||
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
},
|
||||
}
|
||||
|
||||
# select the NDK target
|
||||
arch = 'arm-linux-androideabi'
|
||||
abi_info = android_abis[android_abi]
|
||||
arch = abi_info['arch']
|
||||
|
||||
# the path to the MPD sources
|
||||
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
|
||||
@@ -44,15 +73,16 @@ class AndroidNdkToolchain:
|
||||
self.src_path = src_path
|
||||
self.build_path = build_path
|
||||
|
||||
self.ndk_arch = 'arm'
|
||||
android_abi = 'armeabi-v7a'
|
||||
ndk_platform = 'android-14'
|
||||
ndk_arch = abi_info['ndk_arch']
|
||||
android_api_level = '21'
|
||||
ndk_platform = 'android-' + android_api_level
|
||||
|
||||
# select the NDK compiler
|
||||
gcc_version = '4.9'
|
||||
|
||||
ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
|
||||
sysroot = os.path.join(ndk_platform_path, 'arch-' + self.ndk_arch)
|
||||
sysroot = os.path.join(ndk_path, 'sysroot')
|
||||
target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
|
||||
|
||||
install_prefix = os.path.join(arch_path, 'root')
|
||||
|
||||
@@ -60,11 +90,13 @@ class AndroidNdkToolchain:
|
||||
self.install_prefix = install_prefix
|
||||
self.sysroot = sysroot
|
||||
|
||||
toolchain_path = os.path.join(ndk_path, 'toolchains', arch + '-' + gcc_version, 'prebuilt', build_arch)
|
||||
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
|
||||
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
|
||||
llvm_triple = 'armv7-none-linux-androideabi'
|
||||
llvm_triple = abi_info['llvm_triple']
|
||||
|
||||
common_flags = '-march=armv7-a -mfloat-abi=softfp'
|
||||
common_flags = '-Os -g'
|
||||
common_flags += ' -fPIC'
|
||||
common_flags += ' ' + abi_info['cflags']
|
||||
|
||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||
llvm_bin = os.path.join(llvm_path, 'bin')
|
||||
@@ -72,33 +104,43 @@ class AndroidNdkToolchain:
|
||||
self.cxx = os.path.join(llvm_bin, 'clang++')
|
||||
common_flags += ' -target ' + llvm_triple + ' -integrated-as -gcc-toolchain ' + toolchain_path
|
||||
|
||||
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
||||
|
||||
self.ar = os.path.join(toolchain_bin, arch + '-ar')
|
||||
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
|
||||
self.nm = os.path.join(toolchain_bin, arch + '-nm')
|
||||
self.strip = os.path.join(toolchain_bin, arch + '-strip')
|
||||
|
||||
self.cflags = '-Os -g ' + common_flags
|
||||
self.cxxflags = '-Os -g ' + common_flags
|
||||
self.cppflags = '--sysroot=' + self.sysroot + ' -isystem ' + os.path.join(install_prefix, 'include')
|
||||
self.ldflags = '--sysroot=' + self.sysroot + ' ' + common_flags + ' -L' + os.path.join(install_prefix, 'lib')
|
||||
self.cflags = common_flags
|
||||
self.cxxflags = common_flags
|
||||
self.cppflags = '--sysroot=' + sysroot + \
|
||||
' -isystem ' + os.path.join(install_prefix, 'include') + \
|
||||
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
|
||||
' -D__ANDROID_API__=' + android_api_level
|
||||
self.ldflags = '--sysroot=' + sysroot + \
|
||||
' -L' + os.path.join(install_prefix, 'lib') + \
|
||||
' -L' + os.path.join(target_root, 'usr', 'lib') + \
|
||||
' -B' + os.path.join(target_root, 'usr', 'lib') + \
|
||||
' ' + common_flags
|
||||
self.libs = ''
|
||||
|
||||
self.is_arm = self.ndk_arch == 'arm'
|
||||
self.is_arm = ndk_arch == 'arm'
|
||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||
self.is_aarch64 = ndk_arch == 'arm64'
|
||||
self.is_windows = False
|
||||
|
||||
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
||||
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
||||
|
||||
libstdcxx_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
|
||||
|
||||
if self.is_armv7:
|
||||
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
|
||||
libstdcxx_flags = ''
|
||||
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
|
||||
libstdcxx_libs = '-lc++_static -lc++abi'
|
||||
|
||||
if use_cxx:
|
||||
self.libs += ' ' + libstdcxx_ldadd
|
||||
self.cppflags += ' ' + libstdcxx_cppflags
|
||||
self.cxxflags += ' ' + libstdcxx_cxxflags
|
||||
self.ldflags += ' ' + libstdcxx_ldflags
|
||||
self.libs += ' ' + libstdcxx_libs
|
||||
|
||||
self.env = dict(os.environ)
|
||||
|
||||
@@ -109,14 +151,16 @@ class AndroidNdkToolchain:
|
||||
# a list of third-party libraries to be used by MPD on Android
|
||||
from build.libs import *
|
||||
thirdparty_libs = [
|
||||
libmpdclient,
|
||||
libogg,
|
||||
libvorbis,
|
||||
opus,
|
||||
flac,
|
||||
libid3tag,
|
||||
libmad,
|
||||
ffmpeg,
|
||||
curl,
|
||||
libexpat,
|
||||
libnfs,
|
||||
boost,
|
||||
]
|
||||
|
||||
@@ -154,5 +198,9 @@ configure = [
|
||||
|
||||
] + configure_args
|
||||
|
||||
from build.cmdline import concatenate_cmdline_variables
|
||||
configure = concatenate_cmdline_variables(configure,
|
||||
set(('CFLAGS', 'CXXFLAGS', 'CPPFLAGS', 'LDFLAGS', 'LIBS')))
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env)
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'], env=toolchain.env)
|
||||
|
22
android/res/layout/custom_notification_gb.xml
Normal file
22
android/res/layout/custom_notification_gb.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:padding="10dp" >
|
||||
<ImageView android:id="@+id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginRight="10dp" />
|
||||
<TextView android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/image"
|
||||
style="Custom Notification Title" />
|
||||
<TextView android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/image"
|
||||
android:layout_below="@id/title"
|
||||
style="Custom Notification Text" />
|
||||
</RelativeLayout>
|
5
android/res/layout/log_item.xml
Normal file
5
android/res/layout/log_item.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:typeface="monospace" />
|
37
android/res/layout/settings.xml
Normal file
37
android/res/layout/settings.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/run"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textOn="@string/toggle_button_run_on"
|
||||
android:textOff="@string/toggle_button_run_off" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/run_on_boot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/checkbox_run_on_boot" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/wakelock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/checkbox_wakelock" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/log_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dip" />
|
||||
|
||||
</LinearLayout>
|
@@ -2,4 +2,10 @@
|
||||
|
||||
<resources>
|
||||
<string name="app_name">MPD</string>
|
||||
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
|
||||
<string name="notification_text_mpd_running">Touch for MPD options.</string>
|
||||
<string name="toggle_button_run_on">MPD is running</string>
|
||||
<string name="toggle_button_run_off">MPD is not running</string>
|
||||
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
|
||||
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
|
||||
</resources>
|
||||
|
@@ -25,6 +25,12 @@ import android.content.Context;
|
||||
* Bridge to native code.
|
||||
*/
|
||||
public class Bridge {
|
||||
public static native void run(Context context);
|
||||
|
||||
/* used by jni */
|
||||
public interface LogListener {
|
||||
public void onLog(int priority, String msg);
|
||||
}
|
||||
|
||||
public static native void run(Context context, LogListener logListener);
|
||||
public static native void shutdown();
|
||||
}
|
||||
|
12
android/src/IMain.aidl
Normal file
12
android/src/IMain.aidl
Normal file
@@ -0,0 +1,12 @@
|
||||
package org.musicpd;
|
||||
import org.musicpd.IMainCallback;
|
||||
|
||||
interface IMain
|
||||
{
|
||||
void start();
|
||||
void stop();
|
||||
void setWakelockEnabled(boolean enabled);
|
||||
boolean isRunning();
|
||||
void registerCallback(IMainCallback cb);
|
||||
void unregisterCallback(IMainCallback cb);
|
||||
}
|
9
android/src/IMainCallback.aidl
Normal file
9
android/src/IMainCallback.aidl
Normal file
@@ -0,0 +1,9 @@
|
||||
package org.musicpd;
|
||||
|
||||
interface IMainCallback
|
||||
{
|
||||
void onStarted();
|
||||
void onStopped();
|
||||
void onError(String error);
|
||||
void onLog(int priority, String msg);
|
||||
}
|
@@ -19,57 +19,360 @@
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.widget.TextView;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
public class Main extends Activity implements Runnable {
|
||||
private static final String TAG = "MPD";
|
||||
public class Main extends Service implements Runnable {
|
||||
private static final String TAG = "Main";
|
||||
private static final String REMOTE_ERROR = "MPD process was killed";
|
||||
private static final int MAIN_STATUS_ERROR = -1;
|
||||
private static final int MAIN_STATUS_STOPPED = 0;
|
||||
private static final int MAIN_STATUS_STARTED = 1;
|
||||
|
||||
Thread thread;
|
||||
private static final int MSG_SEND_STATUS = 0;
|
||||
private static final int MSG_SEND_LOG = 1;
|
||||
|
||||
TextView textView;
|
||||
private Thread mThread = null;
|
||||
private int mStatus = MAIN_STATUS_STOPPED;
|
||||
private boolean mAbort = false;
|
||||
private String mError = null;
|
||||
private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>();
|
||||
private final IBinder mBinder = new MainStub(this);
|
||||
private PowerManager.WakeLock mWakelock = null;
|
||||
|
||||
final Handler quitHandler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
textView.setText("Music Player Daemon has quit");
|
||||
static class MainStub extends IMain.Stub {
|
||||
private Main mService;
|
||||
MainStub(Main service) {
|
||||
mService = service;
|
||||
}
|
||||
public void start() {
|
||||
mService.start();
|
||||
}
|
||||
public void stop() {
|
||||
mService.stop();
|
||||
}
|
||||
public void setWakelockEnabled(boolean enabled) {
|
||||
mService.setWakelockEnabled(enabled);
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return mService.isRunning();
|
||||
}
|
||||
public void registerCallback(IMainCallback cb) {
|
||||
mService.registerCallback(cb);
|
||||
}
|
||||
public void unregisterCallback(IMainCallback cb) {
|
||||
mService.unregisterCallback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: what now? restart?
|
||||
private synchronized void sendMessage(int what, int arg1, int arg2, Object obj) {
|
||||
int i = mCallbacks.beginBroadcast();
|
||||
while (i > 0) {
|
||||
i--;
|
||||
final IMainCallback cb = mCallbacks.getBroadcastItem(i);
|
||||
try {
|
||||
switch (what) {
|
||||
case MSG_SEND_STATUS:
|
||||
switch (arg1) {
|
||||
case MAIN_STATUS_ERROR:
|
||||
cb.onError((String)obj);
|
||||
break;
|
||||
case MAIN_STATUS_STOPPED:
|
||||
cb.onStopped();
|
||||
break;
|
||||
case MAIN_STATUS_STARTED:
|
||||
cb.onStarted();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MSG_SEND_LOG:
|
||||
cb.onLog(arg1, (String) obj);
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
mCallbacks.finishBroadcast();
|
||||
}
|
||||
|
||||
private Bridge.LogListener mLogListener = new Bridge.LogListener() {
|
||||
@Override
|
||||
public void onLog(int priority, String msg) {
|
||||
sendMessage(MSG_SEND_LOG, priority, 0, msg);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
start();
|
||||
if (intent != null && intent.getBooleanExtra("wakelock", false))
|
||||
setWakelockEnabled(true);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!Loader.loaded) {
|
||||
final String error = "Failed to load the native MPD libary.\n" +
|
||||
"Report this problem to us, and include the following information:\n" +
|
||||
"SUPPORTED_ABIS=" + String.join(", ", Build.SUPPORTED_ABIS) + "\n" +
|
||||
"PRODUCT=" + Build.PRODUCT + "\n" +
|
||||
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
|
||||
"error=" + Loader.error;
|
||||
setStatus(MAIN_STATUS_ERROR, error);
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (mAbort)
|
||||
return;
|
||||
setStatus(MAIN_STATUS_STARTED, null);
|
||||
}
|
||||
Bridge.run(this, mLogListener);
|
||||
setStatus(MAIN_STATUS_STOPPED, null);
|
||||
}
|
||||
|
||||
private synchronized void setStatus(int status, String error) {
|
||||
mStatus = status;
|
||||
mError = error;
|
||||
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||
}
|
||||
|
||||
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");
|
||||
mainIntent.addCategory("android.intent.category.LAUNCHER");
|
||||
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))
|
||||
.setContentText(getText(R.string.notification_text_mpd_running))
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setContentIntent(contentIntent)
|
||||
.build();
|
||||
|
||||
startForeground(R.string.notification_title_mpd_running, notification);
|
||||
startService(new Intent(this, Main.class));
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
if (mThread != null) {
|
||||
if (mThread.isAlive()) {
|
||||
synchronized (this) {
|
||||
if (mStatus == MAIN_STATUS_STARTED)
|
||||
Bridge.shutdown();
|
||||
else
|
||||
mAbort = true;
|
||||
}
|
||||
}
|
||||
try {
|
||||
mThread.join();
|
||||
mThread = null;
|
||||
mAbort = false;
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
setWakelockEnabled(false);
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
private void setWakelockEnabled(boolean enabled) {
|
||||
if (enabled && mWakelock == null) {
|
||||
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
|
||||
mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||
mWakelock.acquire();
|
||||
Log.d(TAG, "Wakelock acquired");
|
||||
} else if (!enabled && mWakelock != null) {
|
||||
mWakelock.release();
|
||||
mWakelock = null;
|
||||
Log.d(TAG, "Wakelock released");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRunning() {
|
||||
return mThread != null && mThread.isAlive();
|
||||
}
|
||||
|
||||
private void registerCallback(IMainCallback cb) {
|
||||
if (cb != null) {
|
||||
mCallbacks.register(cb);
|
||||
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterCallback(IMainCallback cb) {
|
||||
if (cb != null) {
|
||||
mCallbacks.unregister(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Client that bind the Main Service in order to send commands and receive callback
|
||||
*/
|
||||
public static class Client {
|
||||
|
||||
public interface Callback {
|
||||
public void onStarted();
|
||||
public void onStopped();
|
||||
public void onError(String error);
|
||||
public void onLog(int priority, String msg);
|
||||
}
|
||||
|
||||
private boolean mBound = false;
|
||||
private final Context mContext;
|
||||
private Callback mCallback;
|
||||
private IMain mIMain = null;
|
||||
|
||||
private final IMainCallback.Stub mICallback = new IMainCallback.Stub() {
|
||||
|
||||
@Override
|
||||
public void onStopped() throws RemoteException {
|
||||
mCallback.onStopped();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStarted() throws RemoteException {
|
||||
mCallback.onStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) throws RemoteException {
|
||||
mCallback.onError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLog(int priority, String msg) throws RemoteException {
|
||||
mCallback.onLog(priority, msg);
|
||||
}
|
||||
};
|
||||
|
||||
@Override protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
|
||||
if (!Loader.loaded) {
|
||||
TextView tv = new TextView(this);
|
||||
tv.setText("Failed to load the native MPD libary.\n" +
|
||||
"Report this problem to us, and include the following information:\n" +
|
||||
"ABI=" + Build.CPU_ABI + "\n" +
|
||||
"PRODUCT=" + Build.PRODUCT + "\n" +
|
||||
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
|
||||
"error=" + Loader.error);
|
||||
setContentView(tv);
|
||||
return;
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
synchronized (this) {
|
||||
mIMain = IMain.Stub.asInterface(service);
|
||||
try {
|
||||
if (mCallback != null)
|
||||
mIMain.registerCallback(mICallback);
|
||||
} catch (RemoteException e) {
|
||||
if (mCallback != null)
|
||||
mCallback.onError(REMOTE_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (mCallback != null)
|
||||
mCallback.onError(REMOTE_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
public Client(Context context, Callback cb) throws IllegalArgumentException {
|
||||
if (context == null)
|
||||
throw new IllegalArgumentException("Context can't be null");
|
||||
mContext = context;
|
||||
mCallback = cb;
|
||||
mBound = mContext.bindService(new Intent(mContext, Main.class), mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
if (thread == null || !thread.isAlive()) {
|
||||
thread = new Thread(this, "NativeMain");
|
||||
thread.start();
|
||||
public boolean start() {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
mIMain.start();
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
textView = new TextView(this);
|
||||
textView.setText("Music Player Daemon is running"
|
||||
+ "\nCAUTION: this version is EXPERIMENTAL!");
|
||||
setContentView(textView);
|
||||
public boolean stop() {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
mIMain.stop();
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setWakelockEnabled(boolean enabled) {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
mIMain.setWakelockEnabled(enabled);
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
return mIMain.isRunning();
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (mBound) {
|
||||
synchronized (this) {
|
||||
if (mIMain != null && mICallback != null) {
|
||||
try {
|
||||
if (mCallback != null)
|
||||
mIMain.unregisterCallback(mICallback);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
mBound = false;
|
||||
mContext.unbindService(mServiceConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void run() {
|
||||
Bridge.run(this);
|
||||
quitHandler.sendMessage(quitHandler.obtainMessage());
|
||||
/*
|
||||
* start Main service without any callback
|
||||
*/
|
||||
public static void start(Context context, boolean wakelock) {
|
||||
context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock));
|
||||
}
|
||||
}
|
||||
|
41
android/src/Receiver.java
Normal file
41
android/src/Receiver.java
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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.
|
||||
*/
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class Receiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d("Receiver", "onReceive: " + intent);
|
||||
if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") {
|
||||
if (Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_RUN_ON_BOOT, false)) {
|
||||
final boolean wakelock = Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_WAKELOCK, false);
|
||||
Main.start(context, wakelock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
254
android/src/Settings.java
Normal file
254
android/src/Settings.java
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
public class Settings extends Activity {
|
||||
private static final String TAG = "Settings";
|
||||
private Main.Client mClient;
|
||||
private TextView mTextStatus;
|
||||
private ToggleButton mRunButton;
|
||||
private boolean mFirstRun;
|
||||
private LinkedList<String> mLogListArray = new LinkedList<String>();
|
||||
private ListView mLogListView;
|
||||
private ArrayAdapter<String> mLogListAdapter;
|
||||
|
||||
private static final int MAX_LOGS = 500;
|
||||
|
||||
private static final int MSG_ERROR = 0;
|
||||
private static final int MSG_STOPPED = 1;
|
||||
private static final int MSG_STARTED = 2;
|
||||
private static final int MSG_LOG = 3;
|
||||
|
||||
public static class Preferences {
|
||||
public static final String KEY_RUN_ON_BOOT ="run_on_boot";
|
||||
public static final String KEY_WAKELOCK ="wakelock";
|
||||
|
||||
public static SharedPreferences get(Context context) {
|
||||
return context.getSharedPreferences(TAG, MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void putBoolean(Context context, String key, boolean value) {
|
||||
final SharedPreferences prefs = get(context);
|
||||
|
||||
if (prefs == null)
|
||||
return;
|
||||
final Editor editor = prefs.edit();
|
||||
editor.putBoolean(key, value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static boolean getBoolean(Context context, String key, boolean defValue) {
|
||||
final SharedPreferences prefs = get(context);
|
||||
|
||||
return prefs != null ? prefs.getBoolean(key, defValue) : defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private Handler mHandler = new Handler(new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_ERROR:
|
||||
Log.d(TAG, "onError");
|
||||
|
||||
mClient.release();
|
||||
connectClient();
|
||||
|
||||
mRunButton.setEnabled(false);
|
||||
mRunButton.setChecked(false);
|
||||
|
||||
mTextStatus.setText((String)msg.obj);
|
||||
mFirstRun = true;
|
||||
break;
|
||||
case MSG_STOPPED:
|
||||
Log.d(TAG, "onStopped");
|
||||
mRunButton.setEnabled(true);
|
||||
if (!mFirstRun && Preferences.getBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, false))
|
||||
mRunButton.setChecked(true);
|
||||
else
|
||||
mRunButton.setChecked(false);
|
||||
mFirstRun = true;
|
||||
break;
|
||||
case MSG_STARTED:
|
||||
Log.d(TAG, "onStarted");
|
||||
mRunButton.setChecked(true);
|
||||
mFirstRun = true;
|
||||
mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX
|
||||
break;
|
||||
case MSG_LOG:
|
||||
if (mLogListArray.size() > MAX_LOGS)
|
||||
mLogListArray.remove(0);
|
||||
String priority;
|
||||
switch (msg.arg1) {
|
||||
case Log.DEBUG:
|
||||
priority = "D";
|
||||
break;
|
||||
case Log.ERROR:
|
||||
priority = "E";
|
||||
break;
|
||||
case Log.INFO:
|
||||
priority = "I";
|
||||
break;
|
||||
case Log.VERBOSE:
|
||||
priority = "V";
|
||||
break;
|
||||
case Log.WARN:
|
||||
priority = "W";
|
||||
break;
|
||||
default:
|
||||
priority = "";
|
||||
}
|
||||
mLogListArray.add(priority + "/ " + (String)msg.obj);
|
||||
mLogListAdapter.notifyDataSetChanged();
|
||||
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
private final OnCheckedChangeListener mOnRunChangeListener = new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (mClient != null) {
|
||||
if (isChecked) {
|
||||
mClient.start();
|
||||
if (Preferences.getBoolean(Settings.this,
|
||||
Preferences.KEY_WAKELOCK, false))
|
||||
mClient.setWakelockEnabled(true);
|
||||
} else {
|
||||
mClient.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final OnCheckedChangeListener mOnRunOnBootChangeListener = new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Preferences.putBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, isChecked);
|
||||
if (isChecked && mClient != null && !mRunButton.isChecked())
|
||||
mRunButton.setChecked(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnCheckedChangeListener mOnWakelockChangeListener = new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Preferences.putBoolean(Settings.this, Preferences.KEY_WAKELOCK, isChecked);
|
||||
if (mClient != null && mClient.isRunning())
|
||||
mClient.setWakelockEnabled(isChecked);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setContentView(R.layout.settings);
|
||||
mRunButton = (ToggleButton) findViewById(R.id.run);
|
||||
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
|
||||
|
||||
mTextStatus = (TextView) findViewById(R.id.status);
|
||||
|
||||
mLogListAdapter = new ArrayAdapter<String>(this, R.layout.log_item, mLogListArray);
|
||||
|
||||
mLogListView = (ListView) findViewById(R.id.log_list);
|
||||
mLogListView.setAdapter(mLogListAdapter);
|
||||
mLogListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
|
||||
|
||||
CheckBox checkbox = (CheckBox) findViewById(R.id.run_on_boot);
|
||||
checkbox.setOnCheckedChangeListener(mOnRunOnBootChangeListener);
|
||||
if (Preferences.getBoolean(this, Preferences.KEY_RUN_ON_BOOT, false))
|
||||
checkbox.setChecked(true);
|
||||
|
||||
checkbox = (CheckBox) findViewById(R.id.wakelock);
|
||||
checkbox.setOnCheckedChangeListener(mOnWakelockChangeListener);
|
||||
if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false))
|
||||
checkbox.setChecked(true);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
private void connectClient() {
|
||||
mClient = new Main.Client(this, new Main.Client.Callback() {
|
||||
|
||||
private void removeMessages() {
|
||||
/* don't remove log messages */
|
||||
mHandler.removeMessages(MSG_STOPPED);
|
||||
mHandler.removeMessages(MSG_STARTED);
|
||||
mHandler.removeMessages(MSG_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopped() {
|
||||
removeMessages();
|
||||
mHandler.sendEmptyMessage(MSG_STOPPED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStarted() {
|
||||
removeMessages();
|
||||
mHandler.sendEmptyMessage(MSG_STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
removeMessages();
|
||||
mHandler.sendMessage(Message.obtain(mHandler, MSG_ERROR, error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLog(int priority, String msg) {
|
||||
mHandler.sendMessage(Message.obtain(mHandler, MSG_LOG, priority, 0, msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
mFirstRun = false;
|
||||
connectClient();
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mClient.release();
|
||||
mClient = null;
|
||||
super.onStop();
|
||||
}
|
||||
}
|
38
configure.ac
38
configure.ac
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.20.7, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.20.22, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=20
|
||||
VERSION_REVISION=7
|
||||
VERSION_REVISION=22
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -14,9 +14,9 @@ AM_SILENT_RULES
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
||||
AC_DEFINE(PROTOCOL_VERSION, "0.20.0", [The MPD protocol version])
|
||||
AC_DEFINE(PROTOCOL_VERSION, "0.20.22", [The MPD protocol version])
|
||||
|
||||
GIT_COMMIT=`GIT_DIR="$srcdir/.git" git describe --dirty --always 2>/dev/null`
|
||||
GIT_COMMIT=`cd "$srcdir" && git describe --dirty --always 2>/dev/null`
|
||||
if test x$GIT_COMMIT != x; then
|
||||
AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit])
|
||||
fi
|
||||
@@ -186,6 +186,7 @@ AC_ARG_WITH([android-sdk],
|
||||
[Directory for Android SDK]),
|
||||
[], [with_android_sdk=no])
|
||||
|
||||
android_abi=""
|
||||
if test x$host_is_android = xyes; then
|
||||
if test x$with_android_sdk = xno; then
|
||||
AC_MSG_ERROR([Android build requires option --with-android-sdk=DIR])
|
||||
@@ -194,9 +195,15 @@ if test x$host_is_android = xyes; then
|
||||
if ! test -x $with_android_sdk/tools/android; then
|
||||
AC_MSG_ERROR([Android SDK not found in $with_android_sdk])
|
||||
fi
|
||||
|
||||
AS_CASE([$host_cpu],
|
||||
[i686], [android_abi="x86"],
|
||||
[aarch64], [android_abi="arm64-v8a"],
|
||||
[android_abi="armeabi-v7a"])
|
||||
fi
|
||||
|
||||
AC_SUBST(ANDROID_SDK, [$with_android_sdk])
|
||||
AC_SUBST(ANDROID_ABI, [$android_abi])
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Language Checks
|
||||
@@ -241,6 +248,7 @@ AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
|
||||
AC_CHECK_FUNCS(initgroups)
|
||||
AC_CHECK_FUNCS(fnmatch)
|
||||
AC_CHECK_FUNCS(strndup)
|
||||
AC_CHECK_FUNCS(strcasestr)
|
||||
|
||||
if test x$host_is_linux = xyes; then
|
||||
MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
|
||||
@@ -314,7 +322,7 @@ else
|
||||
fi
|
||||
|
||||
default_enable_daemon=yes
|
||||
if test x$host_is_android = xyes || test x$host_is_android = xyes; then
|
||||
if test x$host_is_android = xyes || test x$host_is_windows = xyes; then
|
||||
default_enable_daemon=no
|
||||
fi
|
||||
AC_ARG_ENABLE(daemon,
|
||||
@@ -401,7 +409,7 @@ AC_ARG_ENABLE(recorder-output,
|
||||
|
||||
AC_ARG_ENABLE(sidplay,
|
||||
AS_HELP_STRING([--enable-sidplay],
|
||||
[enable C64 SID support via libsidplay2]),,
|
||||
[enable C64 SID support via libsidplayfp or libsidplay2]),,
|
||||
enable_sidplay=auto)
|
||||
|
||||
AC_ARG_ENABLE(shout,
|
||||
@@ -453,7 +461,7 @@ dnl ---------------------------------------------------------------------------
|
||||
dnl Mandatory Libraries
|
||||
dnl ---------------------------------------------------------------------------
|
||||
|
||||
AX_BOOST_BASE([1.46],, [AC_MSG_ERROR([Boost not found])])
|
||||
AX_BOOST_BASE([1.54],, [AC_MSG_ERROR([Boost not found])])
|
||||
|
||||
AC_ARG_ENABLE(icu,
|
||||
AS_HELP_STRING([--enable-icu],
|
||||
@@ -491,7 +499,7 @@ if test x$enable_ipv6 = xyes; then
|
||||
AC_EGREP_CPP([AP_maGiC_VALUE],
|
||||
[
|
||||
#include <sys/types.h>
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
@@ -699,7 +707,7 @@ MPD_ENABLE_AUTO_PKG_LIB(smbclient, SMBCLIENT, [smbclient >= 0.2],
|
||||
[smbclient input plugin], [libsmbclient not found])
|
||||
|
||||
dnl ----------------------------------- NFS -----------------------------
|
||||
MPD_ENABLE_AUTO_PKG(nfs, NFS, [libnfs],
|
||||
MPD_ENABLE_AUTO_PKG(nfs, NFS, [libnfs >= 1.9.5],
|
||||
[NFS input plugin], [libnfs not found])
|
||||
|
||||
dnl --------------------------------- Soundcloud ------------------------------
|
||||
@@ -1004,7 +1012,7 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||
[found_sidplay=no])
|
||||
|
||||
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
|
||||
[libsidplay2 not found])
|
||||
[libsidplay2 or libsidutils not found])
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||
@@ -1016,10 +1024,11 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay = xyes; then
|
||||
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
|
||||
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
|
||||
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplayfp or libsidplay2 support])
|
||||
if test x$found_sidplayfp = xyes; then
|
||||
AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2])
|
||||
else
|
||||
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1384,6 +1393,11 @@ then
|
||||
AX_APPEND_COMPILE_FLAGS([-Wcast-qual])
|
||||
AX_APPEND_COMPILE_FLAGS([-Wwrite-strings])
|
||||
AX_APPEND_COMPILE_FLAGS([-Wsign-compare])
|
||||
|
||||
dnl This GCC8 warning for C++17 ABI compatibility is of no
|
||||
dnl interest for us, because we're not a shared library.
|
||||
AX_APPEND_COMPILE_FLAGS([-Wno-noexcept-type])
|
||||
|
||||
AC_LANG_POP
|
||||
fi
|
||||
|
||||
|
@@ -113,7 +113,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_artistid</varname>: the artist id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -122,7 +122,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_albumid</varname>: the album id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -131,7 +131,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_albumartistid</varname>: the album artist
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -140,7 +140,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_trackid</varname>: the track id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -149,7 +149,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_releasetrackid</varname>: the release track
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
@@ -403,6 +403,15 @@
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Change events accumulate, even while the connection is
|
||||
not in "idle" mode; no events gets lost while the client
|
||||
is doing something else with the connection. If an
|
||||
event had already occurred since the last call, the new
|
||||
<command>idle</command> command will return immediately.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
While a client is waiting for <command>idle</command>
|
||||
results, the server disables timeouts, allowing a client
|
||||
@@ -436,7 +445,9 @@
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>volume</varname>:
|
||||
<returnvalue>0-100</returnvalue>
|
||||
<returnvalue>0-100</returnvalue> or
|
||||
<returnvalue>-1</returnvalue> if the volume cannot
|
||||
be determined
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
@@ -1588,6 +1599,11 @@ OK
|
||||
per-artist counts:
|
||||
</para>
|
||||
<programlisting>count group artist</programlisting>
|
||||
<para>
|
||||
A group with an empty value contains counts of matching
|
||||
song which don't this group tag. It exists only if at
|
||||
least one such song is found.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
340
doc/user.xml
340
doc/user.xml
@@ -66,6 +66,26 @@
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="install_android">
|
||||
<title>Installing on Android</title>
|
||||
|
||||
<para>
|
||||
An experimental Android build is available on <ulink
|
||||
url="https://play.google.com/store/apps/details?id=org.musicpd">Google
|
||||
Play</ulink>. After installing and launching it, MPD will
|
||||
scan the music in your <filename>Music</filename> directory
|
||||
and you can control it as usual with a MPD client.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If you need to tweak the configuration, you can create a file
|
||||
called <filename>mpd.conf</filename> on the data partition
|
||||
(the directory which is returned by Android's <ulink
|
||||
url="https://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory()">getExternalStorageDirectory()</ulink>
|
||||
API function).
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="install_source">
|
||||
<title>Compiling from source</title>
|
||||
|
||||
@@ -80,13 +100,40 @@
|
||||
cd mpd-version</programlisting>
|
||||
|
||||
<para>
|
||||
Make sure that all the required libraries and build tools are
|
||||
installed. The <filename>INSTALL</filename> file has a list.
|
||||
In any case, you need:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
a C++14 compiler (e.g. <ulink
|
||||
url="http://gcc.gnu.org/">gcc 4.9</ulink> or <ulink
|
||||
url="http://clang.llvm.org/">clang 3.9</ulink>)
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink url="http://www.boost.org/">Boost 1.54</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink url="https://www.freedesktop.org/wiki/Software/pkg-config/">pkg-config</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Each plugin usually needs a codec library, which you also need
|
||||
to install. Check the plugin reference for details about
|
||||
required libraries.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For example, the following installs a fairly complete list of
|
||||
build dependencies on Debian Wheezy:
|
||||
build dependencies on Debian Jessie:
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
@@ -98,19 +145,20 @@ apt-get install g++ \
|
||||
libmpcdec-dev libwavpack-dev libwildmidi-dev \
|
||||
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
||||
libavcodec-dev libavformat-dev \
|
||||
libmp3lame-dev \
|
||||
libmp3lame-dev libtwolame-dev libshine-dev \
|
||||
libsamplerate0-dev libsoxr-dev \
|
||||
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
|
||||
libzzip-dev \
|
||||
libcurl4-gnutls-dev libyajl-dev libexpat-dev \
|
||||
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
|
||||
libpulse-dev libroar-dev libshout3-dev \
|
||||
libsndio-dev \
|
||||
libmpdclient-dev \
|
||||
libnfs-dev libsmbclient-dev \
|
||||
libupnp-dev \
|
||||
libavahi-client-dev \
|
||||
libsqlite3-dev \
|
||||
libsystemd-daemon-dev libwrap0-dev \
|
||||
libsystemd-dev libwrap0-dev \
|
||||
libcppunit-dev xmlto \
|
||||
libboost-dev \
|
||||
libicu-dev
|
||||
@@ -220,6 +268,59 @@ apt-get install g++ \
|
||||
script.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="android_build">
|
||||
<title>Compiling for Android</title>
|
||||
|
||||
<para>
|
||||
MPD can be compiled as an Android app. It can be installed
|
||||
easily with <link linkend="install_android">Google
|
||||
Play</link>, but if you want to build it from source, follow
|
||||
this section.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
You need:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Android SDK
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink
|
||||
url="https://developer.android.com/ndk/downloads/index.html">Android
|
||||
NDK</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Just like with the native build, unpack the
|
||||
<application>MPD</application> source tarball and change
|
||||
into the directory. Then, instead of
|
||||
<command>./configure</command>, type:
|
||||
</para>
|
||||
|
||||
<programlisting>./android/build.py SDK_PATH NDK_PATH ABI
|
||||
make android/build/mpd-debug.apk</programlisting>
|
||||
|
||||
<para>
|
||||
<varname>SDK_PATH</varname> is the absolute path where you
|
||||
installed the Android SDK; <varname>NDK_PATH</varname> is
|
||||
the Android NDK installation path; <varname>ABI</varname> is
|
||||
the Android ABI to be built, e.g. "armeabi-v7a".
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This downloads various library sources, and then configures
|
||||
and builds <application>MPD</application>.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="systemd_socket">
|
||||
@@ -295,7 +396,9 @@ systemctl start mpd.socket</programlisting>
|
||||
<application>MPD</application> as a user daemon (and not as a
|
||||
system daemon), the configuration is read from
|
||||
<filename>$XDG_CONFIG_HOME/mpd/mpd.conf</filename> (usually
|
||||
<filename>~/.config/mpd/mpd.conf</filename>).
|
||||
<filename>~/.config/mpd/mpd.conf</filename>). On Android,
|
||||
<filename>mpd.conf</filename> will be loaded from the
|
||||
top-level directory of the data partition.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -1035,6 +1138,40 @@ systemctl start mpd.socket</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section id="stickers">
|
||||
<title>The Sticker Database</title>
|
||||
|
||||
<para>
|
||||
"Stickers" are pieces of information attached to songs.
|
||||
Some clients use them to store ratings and other volatile
|
||||
data. This feature requires <ulink
|
||||
url="http://www.sqlite.org/">SQLite</ulink>, compile-time
|
||||
configure option <parameter>--enable-sqlite</parameter>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>sticker_file</varname>
|
||||
<parameter>PATH</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The location of the sticker database.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Resource Limitations</title>
|
||||
|
||||
@@ -1165,6 +1302,55 @@ systemctl start mpd.socket</programlisting>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section id="zeroconf">
|
||||
<title>Zeroconf</title>
|
||||
|
||||
<para>
|
||||
If Zeroconf support (<ulink
|
||||
url="http://avahi.org/">Avahi</ulink> or Apple's Bonjour)
|
||||
was enabled at compile time with
|
||||
<parameter>--with-zeroconf=...</parameter>, MPD can announce
|
||||
its presence on the network. The following settings control
|
||||
this feature:
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<varname>zeroconf_enabled</varname>
|
||||
<parameter>yes|no</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Enables or disables this feature. Default is
|
||||
<parameter>yes</parameter>.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<varname>zeroconf_name</varname>
|
||||
<parameter>NAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The service name to publish via Zeroconf. The
|
||||
default is "<parameter>Music Player</parameter>".
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -1901,13 +2087,6 @@ run</programlisting>
|
||||
database.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note that unless overridden by the below settings (e.g. by
|
||||
setting them to a blank value), general curl configuration
|
||||
from environment variables such as http_proxy or specified
|
||||
in ~/.curlrc will be in effect.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
@@ -1935,6 +2114,15 @@ run</programlisting>
|
||||
<application>MPD</application> instance.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>password</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
The password used to log in to the "master"
|
||||
<application>MPD</application> instance.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>keepalive</varname>
|
||||
@@ -2078,7 +2266,9 @@ run</programlisting>
|
||||
<title><varname>cdio_paranoia</varname></title>
|
||||
|
||||
<para>
|
||||
Plays audio CDs. The URI has the form:
|
||||
Plays audio CDs using <ulink
|
||||
url="http://www.gnu.org/software/libcdio/"><filename>libcdio</filename></ulink>.
|
||||
The URI has the form:
|
||||
"<filename>cdda://[DEVICE][/TRACK]</filename>". The
|
||||
simplest form <filename>cdda://</filename> plays the whole
|
||||
disc in the default drive.
|
||||
@@ -2114,7 +2304,8 @@ run</programlisting>
|
||||
<title><varname>curl</varname></title>
|
||||
|
||||
<para>
|
||||
Opens remote files or streams over HTTP.
|
||||
Opens remote files or streams over HTTP using <ulink
|
||||
url="http://curl.haxx.se/"><filename>libcurl</filename></ulink>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -2207,7 +2398,8 @@ run</programlisting>
|
||||
<title><varname>mms</varname></title>
|
||||
|
||||
<para>
|
||||
Plays streams with the MMS protocol.
|
||||
Plays streams with the MMS protocol using <ulink
|
||||
url="https://launchpad.net/libmms"><filename>libmms</filename></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2263,7 +2455,8 @@ run</programlisting>
|
||||
<title><varname>adplug</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes AdLib files.
|
||||
Decodes AdLib files using <ulink
|
||||
url="http://adplug.sourceforge.net/">libadplug</ulink>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
@@ -2294,8 +2487,8 @@ run</programlisting>
|
||||
<title><varname>audiofile</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes WAV and AIFF files using
|
||||
<filename>libaudiofile</filename>.
|
||||
Decodes WAV and AIFF files using <ulink
|
||||
url="http://audiofile.68k.org/"><filename>libaudiofile</filename></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2303,7 +2496,8 @@ run</programlisting>
|
||||
<title><varname>faad</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes AAC files using <filename>libfaad</filename>.
|
||||
Decodes AAC files using <ulink
|
||||
url="http://www.audiocoding.com/"><filename>libfaad</filename></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2311,8 +2505,8 @@ run</programlisting>
|
||||
<title><varname>ffmpeg</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes various codecs using
|
||||
<application>FFmpeg</application>.
|
||||
Decodes various codecs using <ulink
|
||||
url="https://ffmpeg.org/"><application>FFmpeg</application></ulink>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
@@ -2363,7 +2557,7 @@ run</programlisting>
|
||||
|
||||
<para>
|
||||
Decodes FLAC files using
|
||||
<application>libFLAC</application>.
|
||||
<ulink url="https://xiph.org/flac/"><application>libFLAC</application></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2483,7 +2677,8 @@ run</programlisting>
|
||||
<title><varname>mad</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes MP3 files using <application>libmad</application>.
|
||||
Decodes MP3 files using <ulink
|
||||
url="http://www.underbit.com/products/mad/"><application>libmad</application></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2563,8 +2758,8 @@ run</programlisting>
|
||||
<title><varname>mpcdec</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes Musepack files using
|
||||
<application>libmpcdec</application>.
|
||||
Decodes Musepack files using <ulink
|
||||
url="http://www.musepack.net/"><application>libmpcdec</application></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2572,7 +2767,17 @@ run</programlisting>
|
||||
<title><varname>mpg123</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes MP3 files using <application>libmpg123</application>.
|
||||
Decodes MP3 files using <ulink
|
||||
url="http://www.mpg123.de/"><application>libmpg123</application></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="opus_decoder">
|
||||
<title><varname>opus</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes Opus files using <ulink
|
||||
url="http://www.opus-codec.org/"><application>libopus</application></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2592,8 +2797,8 @@ run</programlisting>
|
||||
<title><varname>sidplay</varname></title>
|
||||
|
||||
<para>
|
||||
C64 SID decoder based on
|
||||
<application>libsidplay</application>.
|
||||
C64 SID decoder based on <ulink
|
||||
url="http://sidplay2.sourceforge.net/"><application>libsidplay</application></ulink>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
@@ -2649,8 +2854,8 @@ run</programlisting>
|
||||
<title><varname>sndfile</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes WAV and AIFF files using
|
||||
<filename>libsndfile</filename>.
|
||||
Decodes WAV and AIFF files using <ulink
|
||||
url="http://www.mega-nerd.com/libsndfile/"><filename>libsndfile</filename></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2658,8 +2863,8 @@ run</programlisting>
|
||||
<title><varname>vorbis</varname></title>
|
||||
|
||||
<para>
|
||||
Decodes Ogg-Vorbis files using
|
||||
<application>libvorbis</application>.
|
||||
Decodes Ogg-Vorbis files using <ulink
|
||||
url="http://www.xiph.org/ogg/vorbis/"><application>libvorbis</application></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2668,7 +2873,7 @@ run</programlisting>
|
||||
|
||||
<para>
|
||||
Decodes WavPack files using
|
||||
<application>libwavpack</application>.
|
||||
<ulink url="http://www.wavpack.com/"><application>libwavpack</application></ulink>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -2863,6 +3068,60 @@ run</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section id="opus_encoder">
|
||||
<title><varname>opus</varname></title>
|
||||
|
||||
<para>
|
||||
Encodes into <ulink
|
||||
url="http://www.opus-codec.org/">Ogg Opus</ulink>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>bitrate</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the data rate in bit per second. The special
|
||||
value "auto" lets <application>libopus</application>
|
||||
choose a rate (which is the default), and "max" uses
|
||||
the maximum possible data rate.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<varname>complexity</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the <ulink
|
||||
url="https://wiki.xiph.org/OpusFAQ#What_is_the_complexity_of_Opus.3F">Opus
|
||||
complexity</ulink>.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<varname>signal</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the Opus signal type. Valid values are "auto"
|
||||
(the default), "voice" and "music".
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section id="vorbis_encoder">
|
||||
<title><varname>vorbis</varname></title>
|
||||
|
||||
@@ -2886,8 +3145,8 @@ run</programlisting>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the quality for VBR. -1 is the lowest quality,
|
||||
10 is the highest quality. Cannot be used with
|
||||
<varname>bitrate</varname>.
|
||||
10 is the highest quality. Defaults to 3. Cannot
|
||||
be used with <varname>bitrate</varname>.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
@@ -3492,7 +3751,7 @@ run</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="jack_output">
|
||||
<title><varname>jack</varname></title>
|
||||
|
||||
<para>
|
||||
@@ -3772,7 +4031,7 @@ run</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="openal_output">
|
||||
<title><varname>openal</varname></title>
|
||||
|
||||
<para>
|
||||
@@ -3923,7 +4182,7 @@ run</programlisting>
|
||||
<para>
|
||||
The <varname>pulse</varname> plugin connects to a <ulink
|
||||
url="http://www.freedesktop.org/wiki/Software/PulseAudio/"><application>PulseAudio</application></ulink>
|
||||
server.
|
||||
server. Requires <filename>libpulse</filename>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
@@ -4111,7 +4370,8 @@ run</programlisting>
|
||||
url="http://www.shoutcast.com/"><application>ShoutCast</application></ulink>
|
||||
or <ulink
|
||||
url="http://icecast.org/"><application>IceCast</application></ulink>
|
||||
server. It forwards tags to this server.
|
||||
server using <filename>libshout</filename>. It forwards
|
||||
tags to this server.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@@ -1,24 +1,37 @@
|
||||
import os.path, subprocess
|
||||
import os.path, subprocess, sys
|
||||
|
||||
from build.project import Project
|
||||
from build.makeproject import MakeProject
|
||||
|
||||
class AutotoolsProject(Project):
|
||||
class AutotoolsProject(MakeProject):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
autogen=False,
|
||||
autoreconf=False,
|
||||
cppflags='',
|
||||
ldflags='',
|
||||
libs='',
|
||||
subdirs=None,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
MakeProject.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
self.autogen = autogen
|
||||
self.autoreconf = autoreconf
|
||||
self.cppflags = cppflags
|
||||
self.ldflags = ldflags
|
||||
self.libs = libs
|
||||
self.subdirs = subdirs
|
||||
|
||||
def build(self, toolchain):
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
if self.autogen:
|
||||
subprocess.check_call(['libtoolize', '--force'], cwd=src)
|
||||
if sys.platform == 'darwin':
|
||||
subprocess.check_call(['glibtoolize', '--force'], cwd=src)
|
||||
else:
|
||||
subprocess.check_call(['libtoolize', '--force'], cwd=src)
|
||||
subprocess.check_call(['aclocal'], cwd=src)
|
||||
subprocess.check_call(['automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
|
||||
subprocess.check_call(['autoconf'], cwd=src)
|
||||
if self.autoreconf:
|
||||
subprocess.check_call(['autoreconf', '-vif'], cwd=src)
|
||||
|
||||
build = self.make_build_path(toolchain)
|
||||
|
||||
@@ -29,8 +42,8 @@ class AutotoolsProject(Project):
|
||||
'CFLAGS=' + toolchain.cflags,
|
||||
'CXXFLAGS=' + toolchain.cxxflags,
|
||||
'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
|
||||
'LDFLAGS=' + toolchain.ldflags,
|
||||
'LIBS=' + toolchain.libs,
|
||||
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
|
||||
'LIBS=' + toolchain.libs + ' ' + self.libs,
|
||||
'AR=' + toolchain.ar,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'STRIP=' + toolchain.strip,
|
||||
@@ -40,7 +53,12 @@ class AutotoolsProject(Project):
|
||||
] + self.configure_args
|
||||
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'],
|
||||
cwd=build, env=toolchain.env)
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
if self.subdirs is not None:
|
||||
for subdir in self.subdirs:
|
||||
MakeProject.build(self, toolchain, os.path.join(build, subdir))
|
||||
else:
|
||||
MakeProject.build(self, toolchain, build)
|
||||
|
29
python/build/cmdline.py
Normal file
29
python/build/cmdline.py
Normal file
@@ -0,0 +1,29 @@
|
||||
def concatenate_cmdline_variables(src, names):
|
||||
"""Find duplicate variable declarations on the given source list, and
|
||||
concatenate the values of those in the 'names' list."""
|
||||
|
||||
# the result list being constructed
|
||||
dest = []
|
||||
|
||||
# a map of variable name to destination list index
|
||||
positions = {}
|
||||
|
||||
for item in src:
|
||||
i = item.find('=')
|
||||
if i > 0:
|
||||
# it's a variable
|
||||
name = item[:i]
|
||||
if name in names:
|
||||
# it's a known variable
|
||||
if name in positions:
|
||||
# already specified: concatenate instead of
|
||||
# appending it
|
||||
dest[positions[name]] += ' ' + item[i + 1:]
|
||||
continue
|
||||
else:
|
||||
# not yet seen: append it and remember the list
|
||||
# index
|
||||
positions[name] = len(dest)
|
||||
dest.append(item)
|
||||
|
||||
return dest
|
@@ -21,6 +21,8 @@ class FfmpegProject(Project):
|
||||
|
||||
if toolchain.is_arm:
|
||||
arch = 'arm'
|
||||
elif toolchain.is_aarch64:
|
||||
arch = 'aarch64'
|
||||
else:
|
||||
arch = 'x86'
|
||||
|
||||
|
@@ -1,28 +1,54 @@
|
||||
import re
|
||||
from os.path import abspath
|
||||
|
||||
from build.project import Project
|
||||
from build.zlib import ZlibProject
|
||||
from build.meson import MesonProject
|
||||
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.16.tar.xz',
|
||||
'fa6bdab67c0e0490302b38f00c27b4959735c3ec8aef7a88327adb1407654464',
|
||||
'lib/libmpdclient.a',
|
||||
)
|
||||
|
||||
libogg = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz',
|
||||
'5c3a34309d8b98640827e5d0991a4015',
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
|
||||
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
|
||||
'lib/libogg.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
)
|
||||
|
||||
libvorbis = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz',
|
||||
'28cb28097c07a735d6af56e598e1c90f',
|
||||
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
|
||||
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
|
||||
'lib/libvorbis.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
|
||||
edits={
|
||||
# this option is not understood by clang
|
||||
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
|
||||
}
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/opus/opus-1.1.4.tar.gz',
|
||||
'9122b6b380081dd2665189f97bfd777f04f92dc3ab6698eea1dbb27ad59d8692',
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
|
||||
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
|
||||
'lib/libopus.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-doc',
|
||||
'--disable-extra-programs',
|
||||
],
|
||||
|
||||
# suppress "visibility default" from opus_defines.h
|
||||
cppflags='-DOPUS_EXPORT=',
|
||||
)
|
||||
|
||||
flac = AutotoolsProject(
|
||||
@@ -32,7 +58,9 @@ flac = AutotoolsProject(
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-xmms-plugin', '--disable-cpplibs',
|
||||
'--disable-doxygen-docs',
|
||||
],
|
||||
subdirs=['include', 'src/libFLAC'],
|
||||
)
|
||||
|
||||
zlib = ZlibProject(
|
||||
@@ -45,21 +73,47 @@ libid3tag = AutotoolsProject(
|
||||
'ftp://ftp.mars.org/pub/mpeg/libid3tag-0.15.1b.tar.gz',
|
||||
'e5808ad997ba32c498803822078748c3',
|
||||
'lib/libid3tag.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
||||
# without this, libid3tag's configure.ac ignores -O* and -f*
|
||||
'--disable-debugging',
|
||||
],
|
||||
autogen=True,
|
||||
|
||||
edits={
|
||||
# fix bug in libid3tag's configure.ac which discards all but the last optimization flag
|
||||
'configure.ac': lambda data: re.sub(r'optimize="\$1"', r'optimize="$optimize $1"', data, count=1),
|
||||
}
|
||||
)
|
||||
|
||||
libmad = AutotoolsProject(
|
||||
'ftp://ftp.mars.org/pub/mpeg/libmad-0.15.1b.tar.gz',
|
||||
'1be543bc30c56fb6bea1d7bf6a64e66c',
|
||||
'lib/libmad.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
||||
# without this, libmad's configure.ac ignores -O* and -f*
|
||||
'--disable-debugging',
|
||||
],
|
||||
autogen=True,
|
||||
)
|
||||
|
||||
liblame = AutotoolsProject(
|
||||
'http://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz',
|
||||
'ddfe36cab873794038ae2c1210557ad34857a4b6bdc515785d1da9e175b1da1e',
|
||||
'lib/libmp3lame.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-gtktest', '--disable-analyzer-hooks',
|
||||
'--disable-decoder', '--disable-frontend',
|
||||
],
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-3.3.tar.xz',
|
||||
'599e7f7c017221c22011c4037b88bdcd1c47cd40c1e466838bc3c465f3e9569d',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.0.2.tar.xz',
|
||||
'a95c0cc9eb990e94031d2183f2e6e444cc61c99f6f182d1575c433d62afb2f97',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -73,17 +127,222 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-swscale',
|
||||
'--disable-postproc',
|
||||
'--disable-avfilter',
|
||||
'--disable-lzo',
|
||||
'--disable-faan',
|
||||
'--disable-pixelutils',
|
||||
'--disable-network',
|
||||
'--disable-encoders',
|
||||
'--disable-muxers',
|
||||
'--disable-protocols',
|
||||
'--disable-outdevs',
|
||||
'--disable-devices',
|
||||
'--disable-filters',
|
||||
'--disable-v4l2_m2m',
|
||||
|
||||
'--disable-parser=bmp',
|
||||
'--disable-parser=cavsvideo',
|
||||
'--disable-parser=dvbsub',
|
||||
'--disable-parser=dvdsub',
|
||||
'--disable-parser=dvd_nav',
|
||||
'--disable-parser=flac',
|
||||
'--disable-parser=g729',
|
||||
'--disable-parser=gsm',
|
||||
'--disable-parser=h261',
|
||||
'--disable-parser=h263',
|
||||
'--disable-parser=h264',
|
||||
'--disable-parser=hevc',
|
||||
'--disable-parser=mjpeg',
|
||||
'--disable-parser=mlp',
|
||||
'--disable-parser=mpeg4video',
|
||||
'--disable-parser=mpegvideo',
|
||||
'--disable-parser=opus',
|
||||
'--disable-parser=vc1',
|
||||
'--disable-parser=vp3',
|
||||
'--disable-parser=vp8',
|
||||
'--disable-parser=vp9',
|
||||
'--disable-parser=png',
|
||||
'--disable-parser=pnm',
|
||||
'--disable-parser=xma',
|
||||
|
||||
'--disable-demuxer=aqtitle',
|
||||
'--disable-demuxer=ass',
|
||||
'--disable-demuxer=bethsoftvid',
|
||||
'--disable-demuxer=bink',
|
||||
'--disable-demuxer=cavsvideo',
|
||||
'--disable-demuxer=cdxl',
|
||||
'--disable-demuxer=dvbsub',
|
||||
'--disable-demuxer=dvbtxt',
|
||||
'--disable-demuxer=h261',
|
||||
'--disable-demuxer=h263',
|
||||
'--disable-demuxer=h264',
|
||||
'--disable-demuxer=ico',
|
||||
'--disable-demuxer=image2',
|
||||
'--disable-demuxer=jacosub',
|
||||
'--disable-demuxer=lrc',
|
||||
'--disable-demuxer=microdvd',
|
||||
'--disable-demuxer=mjpeg',
|
||||
'--disable-demuxer=mjpeg_2000',
|
||||
'--disable-demuxer=mpegps',
|
||||
'--disable-demuxer=mpegvideo',
|
||||
'--disable-demuxer=mpl2',
|
||||
'--disable-demuxer=mpsub',
|
||||
'--disable-demuxer=pjs',
|
||||
'--disable-demuxer=rawvideo',
|
||||
'--disable-demuxer=realtext',
|
||||
'--disable-demuxer=sami',
|
||||
'--disable-demuxer=scc',
|
||||
'--disable-demuxer=srt',
|
||||
'--disable-demuxer=stl',
|
||||
'--disable-demuxer=subviewer',
|
||||
'--disable-demuxer=subviewer1',
|
||||
'--disable-demuxer=swf',
|
||||
'--disable-demuxer=tedcaptions',
|
||||
'--disable-demuxer=vobsub',
|
||||
'--disable-demuxer=vplayer',
|
||||
'--disable-demuxer=webvtt',
|
||||
'--disable-demuxer=yuv4mpegpipe',
|
||||
|
||||
# we don't need these decoders, because we have the dedicated
|
||||
# libraries
|
||||
'--disable-decoder=flac',
|
||||
'--disable-decoder=opus',
|
||||
'--disable-decoder=vorbis',
|
||||
|
||||
# audio codecs nobody uses
|
||||
'--disable-decoder=atrac1',
|
||||
'--disable-decoder=atrac3',
|
||||
'--disable-decoder=atrac3al',
|
||||
'--disable-decoder=atrac3p',
|
||||
'--disable-decoder=atrac3pal',
|
||||
'--disable-decoder=binkaudio_dct',
|
||||
'--disable-decoder=binkaudio_rdft',
|
||||
'--disable-decoder=bmv_audio',
|
||||
'--disable-decoder=dsicinaudio',
|
||||
'--disable-decoder=dvaudio',
|
||||
'--disable-decoder=metasound',
|
||||
'--disable-decoder=paf_audio',
|
||||
'--disable-decoder=ra_144',
|
||||
'--disable-decoder=ra_288',
|
||||
'--disable-decoder=ralf',
|
||||
'--disable-decoder=qdm2',
|
||||
'--disable-decoder=qdmc',
|
||||
|
||||
# disable lots of image and video codecs
|
||||
'--disable-decoder=ass',
|
||||
'--disable-decoder=asv1',
|
||||
'--disable-decoder=asv2',
|
||||
'--disable-decoder=apng',
|
||||
'--disable-decoder=avrn',
|
||||
'--disable-decoder=avrp',
|
||||
'--disable-decoder=bethsoftvid',
|
||||
'--disable-decoder=bink',
|
||||
'--disable-decoder=bmp',
|
||||
'--disable-decoder=bmv_video',
|
||||
'--disable-decoder=cavs',
|
||||
'--disable-decoder=ccaption',
|
||||
'--disable-decoder=cdgraphics',
|
||||
'--disable-decoder=clearvideo',
|
||||
'--disable-decoder=dirac',
|
||||
'--disable-decoder=dsicinvideo',
|
||||
'--disable-decoder=dvbsub',
|
||||
'--disable-decoder=dvdsub',
|
||||
'--disable-decoder=dvvideo',
|
||||
'--disable-decoder=exr',
|
||||
'--disable-decoder=ffv1',
|
||||
'--disable-decoder=ffvhuff',
|
||||
'--disable-decoder=ffwavesynth',
|
||||
'--disable-decoder=flic',
|
||||
'--disable-decoder=flv',
|
||||
'--disable-decoder=fraps',
|
||||
'--disable-decoder=gif',
|
||||
'--disable-decoder=h261',
|
||||
'--disable-decoder=h263',
|
||||
'--disable-decoder=h263i',
|
||||
'--disable-decoder=h263p',
|
||||
'--disable-decoder=h264',
|
||||
'--disable-decoder=hevc',
|
||||
'--disable-decoder=hnm4_video',
|
||||
'--disable-decoder=hq_hqa',
|
||||
'--disable-decoder=hqx',
|
||||
'--disable-decoder=idcin',
|
||||
'--disable-decoder=iff_ilbm',
|
||||
'--disable-decoder=indeo2',
|
||||
'--disable-decoder=indeo3',
|
||||
'--disable-decoder=indeo4',
|
||||
'--disable-decoder=indeo5',
|
||||
'--disable-decoder=interplay_video',
|
||||
'--disable-decoder=jacosub',
|
||||
'--disable-decoder=jpeg2000',
|
||||
'--disable-decoder=jpegls',
|
||||
'--disable-decoder=microdvd',
|
||||
'--disable-decoder=mimic',
|
||||
'--disable-decoder=mjpeg',
|
||||
'--disable-decoder=mmvideo',
|
||||
'--disable-decoder=mpl2',
|
||||
'--disable-decoder=motionpixels',
|
||||
'--disable-decoder=mpeg1video',
|
||||
'--disable-decoder=mpeg2video',
|
||||
'--disable-decoder=mpeg4',
|
||||
'--disable-decoder=mpegvideo',
|
||||
'--disable-decoder=mscc',
|
||||
'--disable-decoder=msmpeg4_crystalhd',
|
||||
'--disable-decoder=msmpeg4v1',
|
||||
'--disable-decoder=msmpeg4v2',
|
||||
'--disable-decoder=msmpeg4v3',
|
||||
'--disable-decoder=msvideo1',
|
||||
'--disable-decoder=mszh',
|
||||
'--disable-decoder=mvc1',
|
||||
'--disable-decoder=mvc2',
|
||||
'--disable-decoder=on2avc',
|
||||
'--disable-decoder=paf_video',
|
||||
'--disable-decoder=png',
|
||||
'--disable-decoder=qdraw',
|
||||
'--disable-decoder=qpeg',
|
||||
'--disable-decoder=rawvideo',
|
||||
'--disable-decoder=realtext',
|
||||
'--disable-decoder=roq',
|
||||
'--disable-decoder=roq_dpcm',
|
||||
'--disable-decoder=rscc',
|
||||
'--disable-decoder=rv10',
|
||||
'--disable-decoder=rv20',
|
||||
'--disable-decoder=rv30',
|
||||
'--disable-decoder=rv40',
|
||||
'--disable-decoder=sami',
|
||||
'--disable-decoder=sheervideo',
|
||||
'--disable-decoder=snow',
|
||||
'--disable-decoder=srt',
|
||||
'--disable-decoder=stl',
|
||||
'--disable-decoder=subrip',
|
||||
'--disable-decoder=subviewer',
|
||||
'--disable-decoder=subviewer1',
|
||||
'--disable-decoder=svq1',
|
||||
'--disable-decoder=svq3',
|
||||
'--disable-decoder=tiff',
|
||||
'--disable-decoder=tiertexseqvideo',
|
||||
'--disable-decoder=truemotion1',
|
||||
'--disable-decoder=truemotion2',
|
||||
'--disable-decoder=truemotion2rt',
|
||||
'--disable-decoder=twinvq',
|
||||
'--disable-decoder=utvideo',
|
||||
'--disable-decoder=vc1',
|
||||
'--disable-decoder=vmdvideo',
|
||||
'--disable-decoder=vp3',
|
||||
'--disable-decoder=vp5',
|
||||
'--disable-decoder=vp6',
|
||||
'--disable-decoder=vp7',
|
||||
'--disable-decoder=vp8',
|
||||
'--disable-decoder=vp9',
|
||||
'--disable-decoder=vqa',
|
||||
'--disable-decoder=webvtt',
|
||||
'--disable-decoder=wmv1',
|
||||
'--disable-decoder=wmv2',
|
||||
'--disable-decoder=wmv3',
|
||||
'--disable-decoder=yuv4',
|
||||
],
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.54.0.tar.lzma',
|
||||
'cd6aa6039f13e0b06e0a93e1b93754f6dc07f444812bb6c32be75a8f28c4070a',
|
||||
'http://curl.haxx.se/download/curl-7.61.1.tar.xz',
|
||||
'3d5913d6a39bd22e68e34dff697fd6e4c3c81563f580c76fca2009315cd81891',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -94,16 +353,46 @@ curl = AutotoolsProject(
|
||||
'--disable-ldap', '--disable-ldaps',
|
||||
'--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
|
||||
'--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
|
||||
'--disable-smb',
|
||||
'--disable-gopher',
|
||||
'--disable-manual',
|
||||
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
|
||||
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
|
||||
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
|
||||
],
|
||||
|
||||
patches='src/lib/curl/patches',
|
||||
)
|
||||
|
||||
libexpat = AutotoolsProject(
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
|
||||
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
|
||||
'lib/libexpat.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--without-docbook',
|
||||
],
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-3.0.0.tar.gz',
|
||||
'445d92c5fc55e4a5b115e358e60486cf8f87ee50e0103d46a02e7fb4618566a5',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-debug',
|
||||
|
||||
# work around -Wtautological-compare
|
||||
'--disable-werror',
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-3.0.0',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.64.0/boost_1_64_0.tar.bz2',
|
||||
'7bcc5caace97baa948931d712ea5f37038dbb1c5d89b43ad4def4ed7cb683332',
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.68.0/boost_1_68_0.tar.bz2',
|
||||
'7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
28
python/build/makeproject.py
Normal file
28
python/build/makeproject.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
|
||||
class MakeProject(Project):
|
||||
def __init__(self, url, md5, installed,
|
||||
install_target='install',
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.install_target = install_target
|
||||
|
||||
def get_simultaneous_jobs(self):
|
||||
return 12
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
|
||||
|
||||
def get_make_install_args(self, toolchain):
|
||||
return ['--quiet', self.install_target]
|
||||
|
||||
def make(self, toolchain, wd, args):
|
||||
subprocess.check_call(['/usr/bin/make'] + args,
|
||||
cwd=wd, env=toolchain.env)
|
||||
|
||||
def build(self, toolchain, wd, install=True):
|
||||
self.make(toolchain, wd, self.get_make_args(toolchain))
|
||||
if install:
|
||||
self.make(toolchain, wd, self.get_make_install_args(toolchain))
|
102
python/build/meson.py
Normal file
102
python/build/meson.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import os.path, subprocess, sys
|
||||
|
||||
from build.project import Project
|
||||
|
||||
class MesonProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
|
||||
def _make_cross_file(self, toolchain):
|
||||
if toolchain.is_windows:
|
||||
system = 'windows'
|
||||
else:
|
||||
system = 'linux'
|
||||
|
||||
if toolchain.is_arm:
|
||||
cpu_family = 'arm'
|
||||
if toolchain.is_armv7:
|
||||
cpu = 'armv7'
|
||||
else:
|
||||
cpu = 'armv6'
|
||||
elif toolchain.is_aarch64:
|
||||
cpu_family = 'aarch64'
|
||||
cpu = 'arm64-v8a'
|
||||
else:
|
||||
cpu_family = 'x86'
|
||||
if 'x86_64' in toolchain.arch:
|
||||
cpu = 'x86_64'
|
||||
else:
|
||||
cpu = 'i686'
|
||||
|
||||
# TODO: support more CPUs
|
||||
endian = 'little'
|
||||
|
||||
# TODO: write pkg-config wrapper
|
||||
|
||||
path = os.path.join(toolchain.build_path, 'meson.cross')
|
||||
os.makedirs(toolchain.build_path, exist_ok=True)
|
||||
with open(path, 'w') as f:
|
||||
f.write("""
|
||||
[binaries]
|
||||
c = '%s'
|
||||
cpp = '%s'
|
||||
ar = '%s'
|
||||
strip = '%s'
|
||||
|
||||
[properties]
|
||||
root = '%s'
|
||||
|
||||
c_args = %s
|
||||
c_link_args = %s
|
||||
|
||||
cpp_args = %s
|
||||
cpp_link_args = %s
|
||||
|
||||
# Keep Meson from executing Android-x86 test binariees
|
||||
needs_exe_wrapper = true
|
||||
|
||||
[host_machine]
|
||||
system = '%s'
|
||||
cpu_family = '%s'
|
||||
cpu = '%s'
|
||||
endian = '%s'
|
||||
""" % (toolchain.cc, toolchain.cxx, toolchain.ar, toolchain.strip,
|
||||
toolchain.install_prefix,
|
||||
repr((toolchain.cppflags + ' ' + toolchain.cflags).split()),
|
||||
repr(toolchain.ldflags.split()),
|
||||
repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split()),
|
||||
repr(toolchain.ldflags.split()),
|
||||
system, cpu_family, cpu, endian))
|
||||
return path
|
||||
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
cross_file = self._make_cross_file(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
configure = [
|
||||
'meson',
|
||||
src, build,
|
||||
|
||||
'--prefix', toolchain.install_prefix,
|
||||
|
||||
# this is necessary because Meson uses Debian's build machine
|
||||
# MultiArch path (e.g. "lib/x86_64-linux-gnu") for cross
|
||||
# builds, which is obviously wrong
|
||||
'--libdir', 'lib',
|
||||
|
||||
'--buildtype', 'plain',
|
||||
|
||||
'--default-library=static',
|
||||
|
||||
'--cross-file', cross_file,
|
||||
] + self.configure_args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
@@ -3,10 +3,13 @@ import re
|
||||
|
||||
from build.download import download_and_verify
|
||||
from build.tar import untar
|
||||
from build.quilt import push_all
|
||||
|
||||
class Project:
|
||||
def __init__(self, url, md5, installed, name=None, version=None,
|
||||
base=None,
|
||||
patches=None,
|
||||
edits=None,
|
||||
use_cxx=False):
|
||||
if base is None:
|
||||
basename = os.path.basename(url)
|
||||
@@ -17,7 +20,7 @@ class Project:
|
||||
self.base = base
|
||||
|
||||
if name is None or version is None:
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?)$', self.base)
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*)$', self.base)
|
||||
if name is None: name = m.group(1)
|
||||
if version is None: version = m.group(2)
|
||||
|
||||
@@ -28,6 +31,11 @@ class Project:
|
||||
self.md5 = md5
|
||||
self.installed = installed
|
||||
|
||||
if patches is not None:
|
||||
srcdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
patches = os.path.join(srcdir, patches)
|
||||
self.patches = patches
|
||||
self.edits = edits
|
||||
self.use_cxx = use_cxx
|
||||
|
||||
def download(self, toolchain):
|
||||
@@ -47,7 +55,21 @@ class Project:
|
||||
parent_path = toolchain.src_path
|
||||
else:
|
||||
parent_path = toolchain.build_path
|
||||
return untar(self.download(toolchain), parent_path, self.base)
|
||||
path = untar(self.download(toolchain), parent_path, self.base)
|
||||
|
||||
if self.patches is not None:
|
||||
push_all(toolchain, path, self.patches)
|
||||
|
||||
if self.edits is not None:
|
||||
for filename, function in self.edits.items():
|
||||
with open(os.path.join(path, filename), 'r+t') as f:
|
||||
old_data = f.read()
|
||||
new_data = function(old_data)
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
f.write(new_data)
|
||||
|
||||
return path
|
||||
|
||||
def make_build_path(self, toolchain):
|
||||
path = os.path.join(toolchain.build_path, self.base)
|
||||
|
9
python/build/quilt.py
Normal file
9
python/build/quilt.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import subprocess
|
||||
|
||||
def run_quilt(toolchain, cwd, patches_path, *args):
|
||||
env = dict(toolchain.env)
|
||||
env['QUILT_PATCHES'] = patches_path
|
||||
subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
|
||||
|
||||
def push_all(toolchain, src_path, patches_path):
|
||||
run_quilt(toolchain, src_path, patches_path, 'push', '-a')
|
@@ -19,9 +19,9 @@
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void
|
||||
AudioFormat::ApplyMask(AudioFormat mask) noexcept
|
||||
@@ -44,21 +44,16 @@ AudioFormat::ApplyMask(AudioFormat mask) noexcept
|
||||
StringBuffer<24>
|
||||
ToString(const AudioFormat af) noexcept
|
||||
{
|
||||
StringBuffer<24> buffer;
|
||||
|
||||
if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
|
||||
af.sample_rate % 44100 == 0) {
|
||||
/* use shortcuts such as "dsd64" which implies the
|
||||
sample rate */
|
||||
snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u",
|
||||
af.sample_rate * 8 / 44100,
|
||||
af.channels);
|
||||
return buffer;
|
||||
return StringFormat<24>("dsd%u:%u",
|
||||
af.sample_rate * 8 / 44100,
|
||||
af.channels);
|
||||
}
|
||||
|
||||
snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u",
|
||||
af.sample_rate, sample_format_to_string(af.format),
|
||||
af.channels);
|
||||
|
||||
return buffer;
|
||||
return StringFormat<24>("%u:%s:%u",
|
||||
af.sample_rate, sample_format_to_string(af.format),
|
||||
af.channels);
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@
|
||||
#include "pcm/SampleFormat.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -127,7 +126,7 @@ struct AudioFormat {
|
||||
void ApplyMask(AudioFormat mask) noexcept;
|
||||
|
||||
gcc_pure
|
||||
AudioFormat WithMask(AudioFormat mask) const {
|
||||
AudioFormat WithMask(AudioFormat mask) const noexcept {
|
||||
AudioFormat result = *this;
|
||||
result.ApplyMask(mask);
|
||||
return result;
|
||||
|
@@ -67,7 +67,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
#define CONFIG_FILE_LOCATION PATH_LITERAL("mpd\\mpd.conf")
|
||||
#define APP_CONFIG_FILE_LOCATION PATH_LITERAL("conf\\mpd.conf")
|
||||
#else
|
||||
@@ -389,7 +389,7 @@ ParseCommandLine(int argc, char **argv, struct options *options)
|
||||
ConfigLoader loader;
|
||||
|
||||
bool found =
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
loader.TryFile(GetUserConfigDir(), CONFIG_FILE_LOCATION) ||
|
||||
loader.TryFile(GetSystemConfigDir(), CONFIG_FILE_LOCATION) ||
|
||||
loader.TryFile(GetAppBaseDir(), APP_CONFIG_FILE_LOCATION);
|
||||
|
@@ -152,7 +152,7 @@ public:
|
||||
bool IsRemote() const noexcept;
|
||||
|
||||
gcc_pure
|
||||
bool IsFile() const {
|
||||
bool IsFile() const noexcept {
|
||||
return !IsRemote();
|
||||
}
|
||||
|
||||
@@ -162,11 +162,11 @@ public:
|
||||
gcc_pure
|
||||
bool IsInDatabase() const noexcept;
|
||||
|
||||
const Tag &GetTag() const {
|
||||
const Tag &GetTag() const noexcept {
|
||||
return tag;
|
||||
}
|
||||
|
||||
Tag &WritableTag() {
|
||||
Tag &WritableTag() noexcept {
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
@@ -27,12 +27,17 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static struct {
|
||||
static struct IOThread {
|
||||
Mutex mutex;
|
||||
Cond cond;
|
||||
|
||||
EventLoop *loop;
|
||||
Thread thread;
|
||||
|
||||
IOThread():thread(BIND_THIS_METHOD(Run)) {}
|
||||
|
||||
private:
|
||||
void Run() noexcept;
|
||||
} io;
|
||||
|
||||
void
|
||||
@@ -44,15 +49,15 @@ io_thread_run(void)
|
||||
io.loop->Run();
|
||||
}
|
||||
|
||||
static void
|
||||
io_thread_func(gcc_unused void *arg)
|
||||
inline void
|
||||
IOThread::Run() noexcept
|
||||
{
|
||||
SetThreadName("io");
|
||||
|
||||
/* lock+unlock to synchronize with io_thread_start(), to be
|
||||
sure that io.thread is set */
|
||||
io.mutex.lock();
|
||||
io.mutex.unlock();
|
||||
mutex.lock();
|
||||
mutex.unlock();
|
||||
|
||||
io_thread_run();
|
||||
}
|
||||
@@ -73,7 +78,7 @@ io_thread_start()
|
||||
assert(!io.thread.IsDefined());
|
||||
|
||||
const std::lock_guard<Mutex> protect(io.mutex);
|
||||
io.thread.Start(io_thread_func, nullptr);
|
||||
io.thread.Start();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -103,8 +108,12 @@ io_thread_get() noexcept
|
||||
return *io.loop;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
bool
|
||||
io_thread_inside() noexcept
|
||||
{
|
||||
return io.thread.IsInside();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#ifndef MPD_IO_THREAD_HXX
|
||||
#define MPD_IO_THREAD_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "Compiler.h"
|
||||
|
||||
class EventLoop;
|
||||
@@ -53,6 +54,8 @@ gcc_const
|
||||
EventLoop &
|
||||
io_thread_get() noexcept;
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
/**
|
||||
* Is the current thread the I/O thread?
|
||||
*/
|
||||
@@ -61,3 +64,5 @@ bool
|
||||
io_thread_inside() noexcept;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "ls.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
#include "storage/StorageInterface.hxx"
|
||||
@@ -83,7 +83,7 @@ LocateUri(const char *uri, const Client *client
|
||||
)
|
||||
{
|
||||
/* skip the obsolete "file://" prefix */
|
||||
const char *path_utf8 = StringAfterPrefix(uri, "file://");
|
||||
const char *path_utf8 = StringAfterPrefixCaseASCII(uri, "file://");
|
||||
if (path_utf8 != nullptr) {
|
||||
if (!PathTraitsUTF8::IsAbsolute(path_utf8))
|
||||
throw std::runtime_error("Malformed file:// URI");
|
||||
|
@@ -24,7 +24,7 @@
|
||||
#include "Compiler.h"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
/* damn you, windows.h! */
|
||||
#ifdef ABSOLUTE
|
||||
|
@@ -34,6 +34,8 @@
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <android/log.h>
|
||||
#include "android/LogListener.hxx"
|
||||
#include "Main.hxx"
|
||||
|
||||
static int
|
||||
ToAndroidLogLevel(LogLevel log_level)
|
||||
@@ -162,7 +164,7 @@ FileLog(const Domain &domain, const char *message)
|
||||
domain.GetName(),
|
||||
chomp_length(message), message);
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
/* force-flush the log file, because setvbuf() does not seem
|
||||
to have an effect on WIN32 */
|
||||
fflush(stderr);
|
||||
@@ -177,6 +179,9 @@ Log(const Domain &domain, LogLevel level, const char *msg)
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
||||
"%s: %s", domain.GetName(), msg);
|
||||
if (logListener != nullptr)
|
||||
logListener->OnLog(Java::GetEnv(), ToAndroidLogLevel(level),
|
||||
"%s: %s", domain.GetName(), msg);
|
||||
#else
|
||||
|
||||
if (level < log_threshold)
|
||||
|
@@ -72,7 +72,7 @@ log_init_file(int line)
|
||||
|
||||
out_fd = open_log_file();
|
||||
if (out_fd < 0) {
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
const std::string out_path_utf8 = out_path.ToUTF8();
|
||||
throw FormatRuntimeError("failed to open log file \"%s\" (config line %d)",
|
||||
out_path_utf8.c_str(), line);
|
||||
@@ -182,7 +182,7 @@ void setup_log_output()
|
||||
fflush(nullptr);
|
||||
|
||||
if (out_fd < 0) {
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
return;
|
||||
#else
|
||||
out_fd = open("/dev/null", O_WRONLY);
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_LOG_LEVEL_HXX
|
||||
#define MPD_LOG_LEVEL_HXX
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
/* damn you, windows.h! */
|
||||
#ifdef ERROR
|
||||
|
76
src/Main.cxx
76
src/Main.cxx
@@ -50,6 +50,7 @@
|
||||
#include "unix/SignalHandlers.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "thread/Slack.hxx"
|
||||
#include "net/Init.hxx"
|
||||
#include "lib/icu/Init.hxx"
|
||||
#include "config/ConfigGlobal.hxx"
|
||||
#include "config/Param.hxx"
|
||||
@@ -91,6 +92,7 @@
|
||||
#include "java/File.hxx"
|
||||
#include "android/Environment.hxx"
|
||||
#include "android/Context.hxx"
|
||||
#include "android/LogListener.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "org_musicpd_Bridge.h"
|
||||
@@ -106,28 +108,28 @@
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
#include <dispatch/dispatch.h>
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
static constexpr size_t KILOBYTE = 1024;
|
||||
static constexpr size_t MEGABYTE = 1024 * KILOBYTE;
|
||||
|
||||
static constexpr size_t DEFAULT_BUFFER_SIZE = 4 * MEGABYTE;
|
||||
static constexpr size_t MIN_BUFFER_SIZE = std::max(CHUNK_SIZE * 32,
|
||||
64 * KILOBYTE);
|
||||
|
||||
static
|
||||
#if GCC_OLDER_THAN(5,0)
|
||||
/* gcc 4.x has no "constexpr" for std::max() */
|
||||
const
|
||||
#else
|
||||
constexpr
|
||||
#endif
|
||||
size_t MIN_BUFFER_SIZE = std::max(CHUNK_SIZE * 32,
|
||||
64 * KILOBYTE);
|
||||
|
||||
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
|
||||
|
||||
#ifdef ANDROID
|
||||
Context *context;
|
||||
LogListener *logListener;
|
||||
#endif
|
||||
|
||||
Instance *instance;
|
||||
@@ -276,25 +278,6 @@ glue_state_file_init()
|
||||
instance->state_file->Read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows-only initialization of the Winsock2 library.
|
||||
*/
|
||||
static void winsock_init(void)
|
||||
{
|
||||
#ifdef WIN32
|
||||
WSADATA sockinfo;
|
||||
|
||||
int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
|
||||
if(retval != 0)
|
||||
FormatFatalError("Attempt to open Winsock2 failed; error code %d",
|
||||
retval);
|
||||
|
||||
if (LOBYTE(sockinfo.wVersion) != 2)
|
||||
FatalError("We use Winsock2 but your version is either too new "
|
||||
"or old; please install Winsock 2.x");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the decoder and player core, including the music pipe.
|
||||
*/
|
||||
@@ -410,7 +393,7 @@ Instance::OnIdle(unsigned flags)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
return win32_main(argc, argv);
|
||||
#else
|
||||
return mpd_main(argc, argv);
|
||||
@@ -443,7 +426,8 @@ try {
|
||||
|
||||
IcuInit();
|
||||
|
||||
winsock_init();
|
||||
const ScopeNetInit net_init;
|
||||
|
||||
io_thread_init();
|
||||
config_global_init();
|
||||
|
||||
@@ -497,21 +481,8 @@ try {
|
||||
daemonize_begin(options.daemon);
|
||||
#endif
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
/* Runs the OS X native event loop in the main thread, and runs
|
||||
the rest of mpd_main on a new thread. This lets CoreAudio receive
|
||||
route change notifications (e.g. plugging or unplugging headphones).
|
||||
All hardware output on OS X ultimately uses CoreAudio internally.
|
||||
This must be run after forking; if dispatch is called before forking,
|
||||
the child process will have a broken internal dispatch state. */
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
exit(mpd_main_after_fork(config));
|
||||
});
|
||||
dispatch_main();
|
||||
return EXIT_FAILURE; // unreachable, because dispatch_main never returns
|
||||
#else
|
||||
return mpd_main_after_fork(config);
|
||||
#endif
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e);
|
||||
return EXIT_FAILURE;
|
||||
@@ -607,7 +578,7 @@ try {
|
||||
playlist_state_restore() */
|
||||
instance->partition->pc.LockUpdateAudio();
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
win32_app_started();
|
||||
#endif
|
||||
|
||||
@@ -622,7 +593,7 @@ try {
|
||||
/* run the main loop */
|
||||
instance->event_loop.Run();
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
win32_app_stopping();
|
||||
#endif
|
||||
|
||||
@@ -694,10 +665,6 @@ try {
|
||||
daemonize_finish();
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
IcuFinish();
|
||||
|
||||
log_deinit();
|
||||
@@ -711,16 +678,19 @@ try {
|
||||
|
||||
gcc_visibility_default
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context)
|
||||
Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logListener)
|
||||
{
|
||||
Java::Init(env);
|
||||
Java::File::Initialise(env);
|
||||
Environment::Initialise(env);
|
||||
|
||||
context = new Context(env, _context);
|
||||
if (_logListener != nullptr)
|
||||
logListener = new LogListener(env, _logListener);
|
||||
|
||||
mpd_main(0, nullptr);
|
||||
|
||||
delete logListener;
|
||||
delete context;
|
||||
Environment::Deinitialise(env);
|
||||
}
|
||||
|
@@ -25,7 +25,10 @@ class Context;
|
||||
struct Instance;
|
||||
|
||||
#ifdef ANDROID
|
||||
#include "android/LogListener.hxx"
|
||||
|
||||
extern Context *context;
|
||||
extern LogListener *logListener;
|
||||
#endif
|
||||
|
||||
extern Instance *instance;
|
||||
@@ -42,7 +45,7 @@ int mpd_main(int argc, char *argv[]);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
|
||||
/**
|
||||
* If program is run as windows service performs nessesary initialization
|
||||
|
@@ -31,45 +31,45 @@ class MixRampInfo {
|
||||
public:
|
||||
MixRampInfo() = default;
|
||||
|
||||
void Clear() {
|
||||
void Clear() noexcept {
|
||||
start.clear();
|
||||
end.clear();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsDefined() const {
|
||||
bool IsDefined() const noexcept {
|
||||
return !start.empty() || !end.empty();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
const char *GetStart() const {
|
||||
const char *GetStart() const noexcept {
|
||||
return start.empty() ? nullptr : start.c_str();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
const char *GetEnd() const {
|
||||
const char *GetEnd() const noexcept {
|
||||
return end.empty() ? nullptr : end.c_str();
|
||||
}
|
||||
|
||||
void SetStart(const char *new_value) {
|
||||
void SetStart(const char *new_value) noexcept {
|
||||
if (new_value == nullptr)
|
||||
start.clear();
|
||||
else
|
||||
start = new_value;
|
||||
}
|
||||
|
||||
void SetStart(std::string &&new_value) {
|
||||
void SetStart(std::string &&new_value) noexcept {
|
||||
start = std::move(new_value);
|
||||
}
|
||||
|
||||
void SetEnd(const char *new_value) {
|
||||
void SetEnd(const char *new_value) noexcept {
|
||||
if (new_value == nullptr)
|
||||
end.clear();
|
||||
else
|
||||
end = new_value;
|
||||
}
|
||||
|
||||
void SetEnd(std::string &&new_value) {
|
||||
void SetEnd(std::string &&new_value) noexcept {
|
||||
end = std::move(new_value);
|
||||
}
|
||||
};
|
||||
|
@@ -60,7 +60,7 @@ public:
|
||||
* music_buffer_new().
|
||||
*/
|
||||
gcc_pure
|
||||
unsigned GetSize() const {
|
||||
unsigned GetSize() const noexcept {
|
||||
return buffer.GetCapacity();
|
||||
}
|
||||
|
||||
|
@@ -79,12 +79,20 @@ struct MusicChunk {
|
||||
*/
|
||||
ReplayGainInfo replay_gain_info;
|
||||
|
||||
/**
|
||||
* A magic value for #replay_gain_serial which omits updating
|
||||
* the #ReplayGainFilter. This is used by "silence" chunks
|
||||
* (see PlayerThread::SendSilence()) so they don't affect the
|
||||
* replay gain.
|
||||
*/
|
||||
static constexpr unsigned IGNORE_REPLAY_GAIN = ~0u;
|
||||
|
||||
/**
|
||||
* A serial number for checking if replay gain info has
|
||||
* changed since the last chunk. The magic value 0 indicates
|
||||
* that there is no replay gain info available.
|
||||
*/
|
||||
unsigned replay_gain_serial = 0;
|
||||
unsigned replay_gain_serial;
|
||||
|
||||
/** the data (probably PCM) */
|
||||
uint8_t data[CHUNK_SIZE];
|
||||
|
@@ -39,7 +39,7 @@ MusicPipe::Contains(const MusicChunk *chunk) const noexcept
|
||||
#endif
|
||||
|
||||
MusicChunk *
|
||||
MusicPipe::Shift()
|
||||
MusicPipe::Shift() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
|
||||
@@ -73,7 +73,7 @@ MusicPipe::Shift()
|
||||
}
|
||||
|
||||
void
|
||||
MusicPipe::Clear(MusicBuffer &buffer)
|
||||
MusicPipe::Clear(MusicBuffer &buffer) noexcept
|
||||
{
|
||||
MusicChunk *chunk;
|
||||
|
||||
@@ -82,7 +82,7 @@ MusicPipe::Clear(MusicBuffer &buffer)
|
||||
}
|
||||
|
||||
void
|
||||
MusicPipe::Push(MusicChunk *chunk)
|
||||
MusicPipe::Push(MusicChunk *chunk) noexcept
|
||||
{
|
||||
assert(!chunk->IsEmpty());
|
||||
assert(chunk->length == 0 || chunk->audio_format.IsValid());
|
||||
|
@@ -77,7 +77,7 @@ public:
|
||||
* audio_format.
|
||||
*/
|
||||
gcc_pure
|
||||
bool CheckFormat(AudioFormat other) const {
|
||||
bool CheckFormat(AudioFormat other) const noexcept {
|
||||
return !audio_format.IsDefined() ||
|
||||
audio_format == other;
|
||||
}
|
||||
@@ -94,37 +94,39 @@ public:
|
||||
* nullptr if the pipe is empty.
|
||||
*/
|
||||
gcc_pure
|
||||
const MusicChunk *Peek() const {
|
||||
const MusicChunk *Peek() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the first chunk from the head, and returns it.
|
||||
*/
|
||||
MusicChunk *Shift();
|
||||
MusicChunk *Shift() noexcept;
|
||||
|
||||
/**
|
||||
* Clears the whole pipe and returns the chunks to the buffer.
|
||||
*
|
||||
* @param buffer the buffer object to return the chunks to
|
||||
*/
|
||||
void Clear(MusicBuffer &buffer);
|
||||
void Clear(MusicBuffer &buffer) noexcept;
|
||||
|
||||
/**
|
||||
* Pushes a chunk to the tail of the pipe.
|
||||
*/
|
||||
void Push(MusicChunk *chunk);
|
||||
void Push(MusicChunk *chunk) noexcept;
|
||||
|
||||
/**
|
||||
* Returns the number of chunks currently in this pipe.
|
||||
*/
|
||||
gcc_pure
|
||||
unsigned GetSize() const {
|
||||
unsigned GetSize() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return size;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsEmpty() const {
|
||||
bool IsEmpty() const noexcept {
|
||||
return GetSize() == 0;
|
||||
}
|
||||
};
|
||||
|
@@ -207,13 +207,12 @@ try {
|
||||
continue;
|
||||
|
||||
#ifdef _UNICODE
|
||||
wchar_t buffer[MAX_PATH];
|
||||
auto result = MultiByteToWideChar(CP_ACP, 0, s, -1,
|
||||
buffer, ARRAY_SIZE(buffer));
|
||||
if (result <= 0)
|
||||
/* on Windows, playlists always contain UTF-8, because
|
||||
its "narrow" charset (i.e. CP_ACP) is incapable of
|
||||
storing all Unicode paths */
|
||||
const auto path = AllocatedPath::FromUTF8(s);
|
||||
if (path.IsNull())
|
||||
continue;
|
||||
|
||||
const Path path = Path::FromFS(buffer);
|
||||
#else
|
||||
const Path path = Path::FromFS(s);
|
||||
#endif
|
||||
|
@@ -28,13 +28,25 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/NarrowPath.hxx"
|
||||
#include "fs/io/FileOutputStream.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
static void
|
||||
playlist_print_path(BufferedOutputStream &os, const Path path)
|
||||
{
|
||||
#ifdef _UNICODE
|
||||
/* on Windows, playlists always contain UTF-8, because its
|
||||
"narrow" charset (i.e. CP_ACP) is incapable of storing all
|
||||
Unicode paths */
|
||||
os.Format("%s\n", path.ToUTF8().c_str());
|
||||
#else
|
||||
os.Format("%s\n", path.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
playlist_print_song(BufferedOutputStream &os, const DetachedSong &song)
|
||||
{
|
||||
@@ -44,7 +56,7 @@ playlist_print_song(BufferedOutputStream &os, const DetachedSong &song)
|
||||
|
||||
try {
|
||||
const auto uri_fs = AllocatedPath::FromUTF8Throw(uri_utf8);
|
||||
os.Format("%s\n", NarrowPath(uri_fs).c_str());
|
||||
playlist_print_path(os, uri_fs);
|
||||
} catch (const std::runtime_error &) {
|
||||
}
|
||||
}
|
||||
@@ -63,7 +75,7 @@ playlist_print_uri(BufferedOutputStream &os, const char *uri)
|
||||
AllocatedPath::FromUTF8Throw(uri);
|
||||
|
||||
if (!path.IsNull())
|
||||
os.Format("%s\n", NarrowPath(path).c_str());
|
||||
playlist_print_path(os, path);
|
||||
} catch (const std::runtime_error &) {
|
||||
}
|
||||
}
|
||||
|
@@ -22,12 +22,15 @@
|
||||
#include "db/LightSong.hxx"
|
||||
#include "DetachedSong.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "tag/Fallback.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/TimeParser.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "lib/icu/Collate.hxx"
|
||||
#include "lib/icu/CaseFold.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -57,22 +60,15 @@ locate_parse_type(const char *str) noexcept
|
||||
return tag_name_parse_i(str);
|
||||
}
|
||||
|
||||
static AllocatedString<>
|
||||
ImportString(const char *p, bool fold_case)
|
||||
{
|
||||
return fold_case
|
||||
? IcuCaseFold(p)
|
||||
: AllocatedString<>::Duplicate(p);
|
||||
}
|
||||
|
||||
SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
|
||||
:tag(_tag), fold_case(_fold_case),
|
||||
value(ImportString(_value, _fold_case))
|
||||
:tag(_tag),
|
||||
value(_value),
|
||||
fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare())
|
||||
{
|
||||
}
|
||||
|
||||
SongFilter::Item::Item(unsigned _tag, time_t _time)
|
||||
:tag(_tag), value(nullptr), time(_time)
|
||||
:tag(_tag), time(_time)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -87,9 +83,7 @@ SongFilter::Item::StringMatch(const char *s) const noexcept
|
||||
assert(tag != LOCATE_TAG_MODIFIED_SINCE);
|
||||
|
||||
if (fold_case) {
|
||||
const auto folded = IcuCaseFold(s);
|
||||
assert(!folded.IsNull());
|
||||
return StringFind(folded.c_str(), value.c_str()) != nullptr;
|
||||
return fold_case.IsIn(s);
|
||||
} else {
|
||||
return StringIsEqual(s, value.c_str());
|
||||
}
|
||||
@@ -116,6 +110,24 @@ SongFilter::Item::Match(const Tag &_tag) const noexcept
|
||||
}
|
||||
|
||||
if (tag < TAG_NUM_OF_ITEM_TYPES && !visited_types[tag]) {
|
||||
bool result = false;
|
||||
if (ApplyTagFallback(TagType(tag),
|
||||
[&](TagType tag2) {
|
||||
if (!visited_types[tag2])
|
||||
return false;
|
||||
|
||||
for (const auto &item : _tag) {
|
||||
if (item.type == tag2 &&
|
||||
StringMatch(item.value)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}))
|
||||
return result;
|
||||
|
||||
/* If the search critieron was not visited during the
|
||||
sweep through the song's tag, it means this field
|
||||
is absent from the tag or empty. Thus, if the
|
||||
@@ -124,15 +136,6 @@ SongFilter::Item::Match(const Tag &_tag) const noexcept
|
||||
true. */
|
||||
if (value.empty())
|
||||
return true;
|
||||
|
||||
if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) {
|
||||
/* if we're looking for "album artist", but
|
||||
only "artist" exists, use that */
|
||||
for (const auto &item : _tag)
|
||||
if (item.type == TAG_ARTIST &&
|
||||
StringMatch(item.value))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -283,3 +286,33 @@ SongFilter::GetBase() const noexcept
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SongFilter
|
||||
SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
|
||||
{
|
||||
const StringView prefix(_prefix);
|
||||
SongFilter result;
|
||||
|
||||
for (const auto &i : items) {
|
||||
if (i.GetTag() == LOCATE_TAG_BASE_TYPE) {
|
||||
const char *s = StringAfterPrefix(i.GetValue(), prefix);
|
||||
if (s != nullptr) {
|
||||
if (*s == 0)
|
||||
continue;
|
||||
|
||||
if (*s == '/') {
|
||||
++s;
|
||||
|
||||
if (*s != 0)
|
||||
result.items.emplace_back(LOCATE_TAG_BASE_TYPE, s);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.items.emplace_back(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -20,9 +20,10 @@
|
||||
#ifndef MPD_SONG_FILTER_HXX
|
||||
#define MPD_SONG_FILTER_HXX
|
||||
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "lib/icu/Compare.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -48,9 +49,12 @@ public:
|
||||
class Item {
|
||||
uint8_t tag;
|
||||
|
||||
bool fold_case;
|
||||
std::string value;
|
||||
|
||||
AllocatedString<> value;
|
||||
/**
|
||||
* This value is only set if case folding is enabled.
|
||||
*/
|
||||
IcuCompare fold_case;
|
||||
|
||||
/**
|
||||
* For #LOCATE_TAG_MODIFIED_SINCE
|
||||
@@ -62,11 +66,6 @@ public:
|
||||
Item(unsigned tag, const char *value, bool fold_case=false);
|
||||
Item(unsigned tag, time_t time);
|
||||
|
||||
Item(const Item &other) = delete;
|
||||
Item(Item &&) = default;
|
||||
|
||||
Item &operator=(const Item &other) = delete;
|
||||
|
||||
unsigned GetTag() const {
|
||||
return tag;
|
||||
}
|
||||
@@ -153,6 +152,13 @@ public:
|
||||
*/
|
||||
gcc_pure
|
||||
const char *GetBase() const noexcept;
|
||||
|
||||
/**
|
||||
* Create a copy of the filter with the given prefix stripped
|
||||
* from all #LOCATE_TAG_BASE_TYPE items. This is used to
|
||||
* filter songs in mounted databases.
|
||||
*/
|
||||
SongFilter WithoutBasePrefix(const char *prefix) const noexcept;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -94,7 +95,7 @@ song_load(TextFile &file, const char *uri)
|
||||
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
|
||||
tag.AddItem(type, value);
|
||||
} else if (strcmp(line, "Time") == 0) {
|
||||
tag.SetDuration(SignedSongTime::FromS(atof(value)));
|
||||
tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
|
||||
} else if (strcmp(line, "Playlist") == 0) {
|
||||
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
|
||||
} else if (strcmp(line, SONG_MTIME) == 0) {
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/FileOutputStream.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "storage/StorageState.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
@@ -56,6 +57,9 @@ StateFile::RememberVersions() noexcept
|
||||
prev_output_version = audio_output_state_get_version();
|
||||
prev_playlist_version = playlist_state_get_hash(partition.playlist,
|
||||
partition.pc);
|
||||
#ifdef ENABLE_DATABASE
|
||||
prev_storage_version = storage_state_get_hash(partition.instance);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -64,7 +68,11 @@ StateFile::IsModified() const noexcept
|
||||
return prev_volume_version != sw_volume_state_get_hash() ||
|
||||
prev_output_version != audio_output_state_get_version() ||
|
||||
prev_playlist_version != playlist_state_get_hash(partition.playlist,
|
||||
partition.pc);
|
||||
partition.pc)
|
||||
#ifdef ENABLE_DATABASE
|
||||
|| prev_storage_version != storage_state_get_hash(partition.instance)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -72,6 +80,11 @@ StateFile::Write(BufferedOutputStream &os)
|
||||
{
|
||||
save_sw_volume_state(os);
|
||||
audio_output_state_save(os, partition.outputs);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
storage_state_save(os, partition.instance);
|
||||
#endif
|
||||
|
||||
playlist_state_save(os, partition.playlist, partition.pc);
|
||||
}
|
||||
|
||||
@@ -123,6 +136,10 @@ try {
|
||||
playlist_state_restore(line, file, song_loader,
|
||||
partition.playlist,
|
||||
partition.pc);
|
||||
#ifdef ENABLE_DATABASE
|
||||
success = success || storage_state_restore(line, file, partition.instance);
|
||||
#endif
|
||||
|
||||
if (!success)
|
||||
FormatError(state_file_domain,
|
||||
"Unrecognized line in state file: %s",
|
||||
|
@@ -46,6 +46,10 @@ class StateFile final : private TimeoutMonitor {
|
||||
unsigned prev_volume_version = 0, prev_output_version = 0,
|
||||
prev_playlist_version = 0;
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
unsigned prev_storage_version = 0;
|
||||
#endif
|
||||
|
||||
public:
|
||||
static constexpr std::chrono::steady_clock::duration DEFAULT_INTERVAL = std::chrono::minutes(2);
|
||||
|
||||
|
@@ -31,7 +31,7 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#ifndef WIN32
|
||||
#ifndef _WIN32
|
||||
/**
|
||||
* The monotonic time stamp when MPD was started. It is used to
|
||||
* calculate the uptime.
|
||||
@@ -114,7 +114,7 @@ stats_print(Response &r, const Partition &partition)
|
||||
{
|
||||
r.Format("uptime: %u\n"
|
||||
"playtime: %lu\n",
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
GetProcessUptimeS(),
|
||||
#else
|
||||
(unsigned)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count(),
|
||||
|
@@ -24,7 +24,7 @@
|
||||
void
|
||||
time_print(Response &r, const char *name, time_t t)
|
||||
{
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
const struct tm *tm2 = gmtime(&t);
|
||||
#else
|
||||
struct tm tm;
|
||||
@@ -35,7 +35,7 @@ time_print(Response &r, const char *name, time_t t)
|
||||
|
||||
char buffer[32];
|
||||
strftime(buffer, sizeof(buffer),
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
"%Y-%m-%dT%H:%M:%SZ",
|
||||
#else
|
||||
"%FT%TZ",
|
||||
|
46
src/android/LogListener.cxx
Normal file
46
src/android/LogListener.cxx
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 "config.h"
|
||||
#include "LogListener.hxx"
|
||||
#include "java/Class.hxx"
|
||||
#include "java/String.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "util/FormatString.hxx"
|
||||
|
||||
void
|
||||
LogListener::OnLog(JNIEnv *env, int priority, const char *fmt, ...) const
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
|
||||
jmethodID method = env->GetMethodID(cls, "onLog",
|
||||
"(ILjava/lang/String;)V");
|
||||
|
||||
assert(method);
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
const auto log = FormatStringV(fmt, args);
|
||||
va_end(args);
|
||||
|
||||
env->CallVoidMethod(Get(), method, priority,
|
||||
Java::String(env, log.c_str()).Get());
|
||||
}
|
32
src/android/LogListener.hxx
Normal file
32
src/android/LogListener.hxx
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MPD_ANDROID_LOG_LISTENER_HXX
|
||||
#define MPD_ANDROID_LOG_LISTENER_HXX
|
||||
|
||||
#include "java/Object.hxx"
|
||||
|
||||
class LogListener : public Java::Object {
|
||||
public:
|
||||
LogListener(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
|
||||
|
||||
void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const;
|
||||
};
|
||||
|
||||
#endif
|
@@ -162,7 +162,7 @@ Bzip2InputStream::FillBuffer()
|
||||
if (bzstream.avail_in > 0)
|
||||
return true;
|
||||
|
||||
size_t count = archive->istream->Read(buffer, sizeof(buffer));
|
||||
size_t count = archive->istream->LockRead(buffer, sizeof(buffer));
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
@@ -174,6 +174,8 @@ Bzip2InputStream::FillBuffer()
|
||||
size_t
|
||||
Bzip2InputStream::Read(void *ptr, size_t length)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
int bz_result;
|
||||
size_t nbytes = 0;
|
||||
|
||||
@@ -224,4 +226,3 @@ const ArchivePlugin bz2_archive_plugin = {
|
||||
bz2_open,
|
||||
bz2_extensions,
|
||||
};
|
||||
|
||||
|
@@ -115,7 +115,12 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
||||
visitor.VisitArchiveEntry(path + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#if LIBCDIO_VERSION_NUM >= 20000
|
||||
iso9660_filelist_free(entlist);
|
||||
#else
|
||||
_cdio_list_free (entlist, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
static ArchiveFile *
|
||||
@@ -182,6 +187,8 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
|
||||
size_t
|
||||
Iso9660InputStream::Read(void *ptr, size_t read_size)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
int readed = 0;
|
||||
int no_blocks, cur_block;
|
||||
size_t left_bytes = statbuf->size - offset;
|
||||
|
@@ -138,6 +138,8 @@ ZzipArchiveFile::OpenStream(const char *pathname,
|
||||
size_t
|
||||
ZzipInputStream::Read(void *ptr, size_t read_size)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
int ret = zzip_file_read(file, ptr, read_size);
|
||||
if (ret < 0)
|
||||
throw std::runtime_error("zzip_file_read() has failed");
|
||||
@@ -155,6 +157,8 @@ ZzipInputStream::IsEOF() noexcept
|
||||
void
|
||||
ZzipInputStream::Seek(offset_type new_offset)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
|
||||
if (ofs < 0)
|
||||
throw std::runtime_error("zzip_seek() has failed");
|
||||
|
@@ -100,7 +100,7 @@ public:
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsExpired() const {
|
||||
bool IsExpired() const noexcept {
|
||||
return !FullyBufferedSocket::IsDefined();
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,7 @@
|
||||
void
|
||||
Client::AllowFile(Path path_fs) const
|
||||
{
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
(void)path_fs;
|
||||
|
||||
throw ProtocolError(ACK_ERROR_PERMISSION, "Access denied");
|
||||
|
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
/* fuck WIN32! */
|
||||
#include <windows.h>
|
||||
#undef GetMessage
|
||||
|
@@ -29,7 +29,7 @@
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
|
@@ -123,6 +123,10 @@ ToAck(std::exception_ptr ep) noexcept
|
||||
return ACK_ERROR_SYSTEM;
|
||||
} catch (const std::invalid_argument &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
} catch (const std::length_error &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
} catch (const std::out_of_range &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
#ifdef GLIBCXX_49X
|
||||
} catch (const std::exception &e) {
|
||||
#else
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_COMMAND_RESULT_HXX
|
||||
#define MPD_COMMAND_RESULT_HXX
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
/* damn you, windows.h! */
|
||||
#ifdef ERROR
|
||||
|
@@ -191,7 +191,7 @@ handle_list(Client &client, Request args, Response &r)
|
||||
}
|
||||
|
||||
std::unique_ptr<SongFilter> filter;
|
||||
tag_mask_t group_mask = 0;
|
||||
TagType group = TAG_NUM_OF_ITEM_TYPES;
|
||||
|
||||
if (args.size == 1) {
|
||||
/* for compatibility with < 0.12.0 */
|
||||
@@ -206,18 +206,16 @@ handle_list(Client &client, Request args, Response &r)
|
||||
args.shift()));
|
||||
}
|
||||
|
||||
while (args.size >= 2 &&
|
||||
StringIsEqual(args[args.size - 2], "group")) {
|
||||
if (args.size >= 2 &&
|
||||
StringIsEqual(args[args.size - 2], "group")) {
|
||||
const char *s = args[args.size - 1];
|
||||
TagType gt = tag_name_parse_i(s);
|
||||
if (gt == TAG_NUM_OF_ITEM_TYPES) {
|
||||
group = tag_name_parse_i(s);
|
||||
if (group == TAG_NUM_OF_ITEM_TYPES) {
|
||||
r.FormatError(ACK_ERROR_ARG,
|
||||
"Unknown tag type: %s", s);
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
group_mask |= tag_mask_t(1) << unsigned(gt);
|
||||
|
||||
args.pop_back();
|
||||
args.pop_back();
|
||||
}
|
||||
@@ -230,14 +228,13 @@ handle_list(Client &client, Request args, Response &r)
|
||||
}
|
||||
}
|
||||
|
||||
if (tagType < TAG_NUM_OF_ITEM_TYPES &&
|
||||
group_mask & (tag_mask_t(1) << tagType)) {
|
||||
if (tagType < TAG_NUM_OF_ITEM_TYPES && tagType == group) {
|
||||
r.Error(ACK_ERROR_ARG, "Conflicting group");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
PrintUniqueTags(r, client.partition,
|
||||
tagType, group_mask, filter.get());
|
||||
tagType, group, filter.get());
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
|
@@ -58,7 +58,7 @@ skip_path(Path name_fs) noexcept
|
||||
return name_fs.HasNewline();
|
||||
}
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
/* PRIu64 causes bogus compiler warning */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat"
|
||||
@@ -101,7 +101,7 @@ handle_listfiles_local(Response &r, Path path_fs)
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
@@ -45,68 +45,57 @@ public:
|
||||
: default_value;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
int ParseInt(unsigned idx) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgInt(data[idx]);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
int ParseInt(unsigned idx, int min_value, int max_value) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgInt(data[idx], min_value, max_value);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
int ParseUnsigned(unsigned idx) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgUnsigned(data[idx]);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
int ParseUnsigned(unsigned idx, unsigned max_value) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgUnsigned(data[idx], max_value);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool ParseBool(unsigned idx) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgBool(data[idx]);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
RangeArg ParseRange(unsigned idx) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgRange(data[idx]);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
float ParseFloat(unsigned idx) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgFloat(data[idx]);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
SongTime ParseSongTime(unsigned idx) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgSongTime(data[idx]);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
SignedSongTime ParseSignedSongTime(unsigned idx) const {
|
||||
assert(idx < size);
|
||||
return ParseCommandArgSignedSongTime(data[idx]);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
int ParseOptional(unsigned idx, int default_value) const {
|
||||
return idx < size
|
||||
? ParseInt(idx)
|
||||
: default_value;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
RangeArg ParseOptional(unsigned idx, RangeArg default_value) const {
|
||||
return idx < size
|
||||
? ParseRange(idx)
|
||||
|
@@ -50,7 +50,7 @@ skip_path(const char *name_utf8) noexcept
|
||||
return strchr(name_utf8, '\n') != nullptr;
|
||||
}
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
/* PRIu64 causes bogus compiler warning */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat"
|
||||
@@ -94,7 +94,7 @@ handle_listfiles_storage(Response &r, StorageDirectoryReader &reader)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
@@ -82,12 +82,12 @@ struct ConfigBlock {
|
||||
* object that was synthesized and not loaded from a
|
||||
* configuration file.
|
||||
*/
|
||||
bool IsNull() const {
|
||||
bool IsNull() const noexcept {
|
||||
return line < 0;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsEmpty() const {
|
||||
bool IsEmpty() const noexcept {
|
||||
return block_params.empty();
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "Compiler.h"
|
||||
|
||||
#if defined(WIN32) && CLANG_OR_GCC_VERSION(4,7)
|
||||
#if defined(_WIN32) && CLANG_OR_GCC_VERSION(4,7)
|
||||
/* "INPUT" is declared by winuser.h */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
@@ -93,7 +93,7 @@ enum class ConfigBlockOption {
|
||||
MAX
|
||||
};
|
||||
|
||||
#if defined(WIN32) && CLANG_OR_GCC_VERSION(4,7)
|
||||
#if defined(_WIN32) && CLANG_OR_GCC_VERSION(4,7)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
@@ -29,7 +29,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef WIN32
|
||||
#ifndef _WIN32
|
||||
#include <pwd.h>
|
||||
|
||||
/**
|
||||
@@ -79,7 +79,7 @@ ParsePath(const char *path)
|
||||
{
|
||||
assert(path != nullptr);
|
||||
|
||||
#ifndef WIN32
|
||||
#ifndef _WIN32
|
||||
if (path[0] == '~') {
|
||||
++path;
|
||||
|
||||
@@ -119,7 +119,7 @@ ParsePath(const char *path)
|
||||
} else {
|
||||
#endif
|
||||
return AllocatedPath::FromUTF8Throw(path);
|
||||
#ifndef WIN32
|
||||
#ifndef _WIN32
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "client/Response.hxx"
|
||||
#include "LightSong.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "tag/VisitFallback.hxx"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
@@ -72,24 +73,15 @@ stats_visitor_song(SearchStats &stats, const LightSong &song)
|
||||
stats.total_duration += duration;
|
||||
}
|
||||
|
||||
static bool
|
||||
CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
|
||||
static void
|
||||
CollectGroupCounts(TagCountMap &map, const Tag &tag,
|
||||
const char *value) noexcept
|
||||
{
|
||||
bool found = false;
|
||||
for (const auto &item : tag) {
|
||||
if (item.type == group) {
|
||||
auto r = map.insert(std::make_pair(item.value,
|
||||
SearchStats()));
|
||||
SearchStats &s = r.first->second;
|
||||
++s.n_songs;
|
||||
if (!tag.duration.IsNegative())
|
||||
s.total_duration += tag.duration;
|
||||
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
auto r = map.insert(std::make_pair(value, SearchStats()));
|
||||
SearchStats &s = r.first->second;
|
||||
++s.n_songs;
|
||||
if (!tag.duration.IsNegative())
|
||||
s.total_duration += tag.duration;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -98,9 +90,10 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
||||
assert(song.tag != nullptr);
|
||||
|
||||
const Tag &tag = *song.tag;
|
||||
if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST)
|
||||
/* fall back to "Artist" if no "AlbumArtist" was found */
|
||||
CollectGroupCounts(map, TAG_ARTIST, tag);
|
||||
VisitTagWithFallbackOrEmpty(tag, group,
|
||||
std::bind(CollectGroupCounts, std::ref(map),
|
||||
std::cref(tag),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -187,22 +187,34 @@ PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song)
|
||||
}
|
||||
|
||||
static void
|
||||
PrintUniqueTag(Response &r, TagType tag_type,
|
||||
const Tag &tag)
|
||||
PrintUniqueTags(Response &r, TagType tag_type,
|
||||
const std::set<std::string> &values)
|
||||
{
|
||||
const char *value = tag.GetValue(tag_type);
|
||||
assert(value != nullptr);
|
||||
r.Format("%s: %s\n", tag_item_names[tag_type], value);
|
||||
const char *const name = tag_item_names[tag_type];
|
||||
for (const auto &i : values)
|
||||
r.Format("%s: %s\n", name, i.c_str());
|
||||
}
|
||||
|
||||
for (const auto &item : tag)
|
||||
if (item.type != tag_type)
|
||||
r.Format("%s: %s\n",
|
||||
tag_item_names[item.type], item.value);
|
||||
static void
|
||||
PrintGroupedUniqueTags(Response &r, TagType tag_type, TagType group,
|
||||
const std::map<std::string, std::set<std::string>> &groups)
|
||||
{
|
||||
if (group == TAG_NUM_OF_ITEM_TYPES) {
|
||||
for (const auto &i : groups)
|
||||
PrintUniqueTags(r, tag_type, i.second);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *const group_name = tag_item_names[group];
|
||||
for (const auto &i : groups) {
|
||||
r.Format("%s: %s\n", group_name, i.first.c_str());
|
||||
PrintUniqueTags(r, tag_type, i.second);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PrintUniqueTags(Response &r, Partition &partition,
|
||||
unsigned type, tag_mask_t group_mask,
|
||||
unsigned type, TagType group,
|
||||
const SongFilter *filter)
|
||||
{
|
||||
const Database &db = partition.GetDatabaseOrThrow();
|
||||
@@ -217,10 +229,9 @@ PrintUniqueTags(Response &r, Partition &partition,
|
||||
} else {
|
||||
assert(type < TAG_NUM_OF_ITEM_TYPES);
|
||||
|
||||
using namespace std::placeholders;
|
||||
const auto f = std::bind(PrintUniqueTag, std::ref(r),
|
||||
(TagType)type, _1);
|
||||
db.VisitUniqueTags(selection, (TagType)type,
|
||||
group_mask, f);
|
||||
PrintGroupedUniqueTags(r, TagType(type), group,
|
||||
db.CollectUniqueTags(selection,
|
||||
TagType(type),
|
||||
group));
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_DB_PRINT_H
|
||||
#define MPD_DB_PRINT_H
|
||||
|
||||
#include "tag/Mask.hxx"
|
||||
#include "tag/TagType.h"
|
||||
|
||||
class SongFilter;
|
||||
struct DatabaseSelection;
|
||||
@@ -44,7 +44,7 @@ db_selection_print(Response &r, Partition &partition,
|
||||
|
||||
void
|
||||
PrintUniqueTags(Response &r, Partition &partition,
|
||||
unsigned type, tag_mask_t group_mask,
|
||||
unsigned type, TagType group,
|
||||
const SongFilter *filter);
|
||||
|
||||
#endif
|
||||
|
@@ -29,7 +29,7 @@
|
||||
|
||||
struct StringLess {
|
||||
gcc_pure
|
||||
bool operator()(const char *a, const char *b) const {
|
||||
bool operator()(const char *a, const char *b) const noexcept {
|
||||
return strcmp(a, b) < 0;
|
||||
}
|
||||
};
|
||||
|
@@ -22,9 +22,12 @@
|
||||
|
||||
#include "Visitor.hxx"
|
||||
#include "tag/TagType.h"
|
||||
#include "tag/Mask.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
struct DatabasePlugin;
|
||||
@@ -99,14 +102,10 @@ public:
|
||||
return Visit(selection, VisitDirectory(), visit_song);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all unique tag values.
|
||||
*/
|
||||
virtual void VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const = 0;
|
||||
virtual std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
TagType group=TAG_NUM_OF_ITEM_TYPES) const = 0;
|
||||
|
||||
gcc_pure
|
||||
virtual DatabaseStats GetStats(const DatabaseSelection &selection) const = 0;
|
||||
|
||||
/**
|
||||
@@ -127,7 +126,7 @@ public:
|
||||
* Returns 0 if that is not not known/available.
|
||||
*/
|
||||
gcc_pure
|
||||
virtual time_t GetUpdateStamp() const = 0;
|
||||
virtual time_t GetUpdateStamp() const noexcept = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -44,16 +44,16 @@ struct LightDirectory {
|
||||
constexpr LightDirectory(const char *_uri, time_t _mtime)
|
||||
:uri(_uri), mtime(_mtime) {}
|
||||
|
||||
static constexpr LightDirectory Root() {
|
||||
static constexpr LightDirectory Root() noexcept {
|
||||
return LightDirectory("", 0);
|
||||
}
|
||||
|
||||
bool IsRoot() const {
|
||||
bool IsRoot() const noexcept {
|
||||
return *uri == 0;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
const char *GetPath() const {
|
||||
const char *GetPath() const noexcept {
|
||||
return uri;
|
||||
}
|
||||
};
|
||||
|
@@ -45,7 +45,7 @@ struct PlaylistInfo {
|
||||
constexpr CompareName(const char *_name):name(_name) {}
|
||||
|
||||
gcc_pure
|
||||
bool operator()(const PlaylistInfo &pi) const {
|
||||
bool operator()(const PlaylistInfo &pi) const noexcept {
|
||||
return pi.name.compare(name) == 0;
|
||||
}
|
||||
};
|
||||
|
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Selection.hxx"
|
||||
#include "SongFilter.hxx"
|
||||
|
||||
|
@@ -20,34 +20,42 @@
|
||||
#include "UniqueTags.hxx"
|
||||
#include "Interface.hxx"
|
||||
#include "LightSong.hxx"
|
||||
#include "tag/Set.hxx"
|
||||
#include "tag/VisitFallback.hxx"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static void
|
||||
CollectTags(TagSet &set, TagType tag_type, tag_mask_t group_mask,
|
||||
const LightSong &song)
|
||||
CollectTags(std::set<std::string> &result,
|
||||
const Tag &tag,
|
||||
TagType tag_type) noexcept
|
||||
{
|
||||
assert(song.tag != nullptr);
|
||||
const Tag &tag = *song.tag;
|
||||
|
||||
set.InsertUnique(tag, tag_type, group_mask);
|
||||
VisitTagWithFallbackOrEmpty(tag, tag_type, [&result](const char *value){
|
||||
result.emplace(value);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||
TagType tag_type, tag_mask_t group_mask,
|
||||
VisitTag visit_tag)
|
||||
static void
|
||||
CollectGroupTags(std::map<std::string, std::set<std::string>> &result,
|
||||
const Tag &tag,
|
||||
TagType tag_type,
|
||||
TagType group) noexcept
|
||||
{
|
||||
TagSet set;
|
||||
|
||||
using namespace std::placeholders;
|
||||
const auto f = std::bind(CollectTags, std::ref(set),
|
||||
tag_type, group_mask, _1);
|
||||
db.Visit(selection, f);
|
||||
|
||||
for (const auto &value : set)
|
||||
visit_tag(value);
|
||||
VisitTagWithFallbackOrEmpty(tag, group, [&](const char *group_name){
|
||||
CollectTags(result[group_name], tag, tag_type);
|
||||
});
|
||||
}
|
||||
|
||||
std::map<std::string, std::set<std::string>>
|
||||
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group)
|
||||
{
|
||||
std::map<std::string, std::set<std::string>> result;
|
||||
|
||||
db.Visit(selection, [&result, tag_type, group](const LightSong &song){
|
||||
CollectGroupTags(result, *song.tag, tag_type, group);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -20,16 +20,19 @@
|
||||
#ifndef MPD_DB_UNIQUE_TAGS_HXX
|
||||
#define MPD_DB_UNIQUE_TAGS_HXX
|
||||
|
||||
#include "Visitor.hxx"
|
||||
#include "tag/TagType.h"
|
||||
#include "tag/Mask.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
class Database;
|
||||
struct DatabaseSelection;
|
||||
|
||||
void
|
||||
VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||
TagType tag_type, tag_mask_t group_mask,
|
||||
VisitTag visit_tag);
|
||||
gcc_pure
|
||||
std::map<std::string, std::set<std::string>>
|
||||
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group);
|
||||
|
||||
#endif
|
||||
|
@@ -82,6 +82,7 @@ class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
|
||||
DatabaseListener &listener;
|
||||
|
||||
const std::string host;
|
||||
const std::string password;
|
||||
const unsigned port;
|
||||
const bool keepalive;
|
||||
|
||||
@@ -119,15 +120,15 @@ public:
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const override;
|
||||
|
||||
void VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const override;
|
||||
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
TagType group) const override;
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
unsigned Update(const char *uri_utf8, bool discard) override;
|
||||
|
||||
time_t GetUpdateStamp() const override {
|
||||
time_t GetUpdateStamp() const noexcept override {
|
||||
return update_stamp;
|
||||
}
|
||||
|
||||
@@ -169,6 +170,13 @@ static constexpr struct {
|
||||
#if LIBMPDCLIENT_CHECK_VERSION(2,10,0)
|
||||
{ TAG_MUSICBRAINZ_RELEASETRACKID,
|
||||
MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
|
||||
#endif
|
||||
#if LIBMPDCLIENT_CHECK_VERSION(2,11,0)
|
||||
{ TAG_ARTIST_SORT, MPD_TAG_ARTIST_SORT },
|
||||
{ TAG_ALBUM_ARTIST_SORT, MPD_TAG_ALBUM_ARTIST_SORT },
|
||||
#endif
|
||||
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
|
||||
{ TAG_ALBUM_SORT, MPD_TAG_ALBUM_SORT },
|
||||
#endif
|
||||
{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
|
||||
};
|
||||
@@ -325,12 +333,32 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
SendGroup(mpd_connection *connection, TagType group)
|
||||
{
|
||||
if (group == TAG_NUM_OF_ITEM_TYPES)
|
||||
return true;
|
||||
|
||||
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
|
||||
const auto tag = Convert(group);
|
||||
if (tag == MPD_TAG_COUNT)
|
||||
throw std::runtime_error("Unsupported tag");
|
||||
|
||||
return mpd_search_add_group_tag(connection, tag);
|
||||
#else
|
||||
(void)connection;
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
||||
const ConfigBlock &block)
|
||||
:Database(proxy_db_plugin),
|
||||
SocketMonitor(_loop), IdleMonitor(_loop),
|
||||
listener(_listener),
|
||||
host(block.GetBlockValue("host", "")),
|
||||
password(block.GetBlockValue("password", "")),
|
||||
port(block.GetBlockValue("port", 0u)),
|
||||
keepalive(block.GetBlockValue("keepalive", false))
|
||||
{
|
||||
@@ -374,6 +402,10 @@ ProxyDatabase::Connect()
|
||||
|
||||
try {
|
||||
CheckError(connection);
|
||||
|
||||
if (!password.empty() &&
|
||||
!mpd_run_password(connection, password.c_str()))
|
||||
ThrowError(connection);
|
||||
} catch (...) {
|
||||
mpd_connection_free(connection);
|
||||
connection = nullptr;
|
||||
@@ -682,7 +714,7 @@ static void
|
||||
SearchSongs(struct mpd_connection *connection,
|
||||
const DatabaseSelection &selection,
|
||||
VisitSong visit_song)
|
||||
{
|
||||
try {
|
||||
assert(selection.recursive);
|
||||
assert(visit_song);
|
||||
|
||||
@@ -709,6 +741,11 @@ SearchSongs(struct mpd_connection *connection,
|
||||
|
||||
if (!mpd_response_finish(connection))
|
||||
ThrowError(connection);
|
||||
} catch (...) {
|
||||
if (connection != nullptr)
|
||||
mpd_search_cancel(connection);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -753,12 +790,10 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
|
||||
visit_directory, visit_song, visit_playlist);
|
||||
}
|
||||
|
||||
void
|
||||
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
gcc_unused tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const
|
||||
{
|
||||
std::map<std::string, std::set<std::string>>
|
||||
ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group) const
|
||||
try {
|
||||
// TODO: eliminate the const_cast
|
||||
const_cast<ProxyDatabase *>(this)->EnsureConnected();
|
||||
|
||||
@@ -767,40 +802,62 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
throw std::runtime_error("Unsupported tag");
|
||||
|
||||
if (!mpd_search_db_tags(connection, tag_type2) ||
|
||||
!SendConstraints(connection, selection))
|
||||
!SendConstraints(connection, selection) ||
|
||||
!SendGroup(connection, group))
|
||||
ThrowError(connection);
|
||||
|
||||
// TODO: use group_mask
|
||||
|
||||
if (!mpd_search_commit(connection))
|
||||
ThrowError(connection);
|
||||
|
||||
while (auto *pair = mpd_recv_pair_tag(connection, tag_type2)) {
|
||||
AtScopeExit(this, pair) {
|
||||
mpd_return_pair(connection, pair);
|
||||
};
|
||||
std::map<std::string, std::set<std::string>> result;
|
||||
|
||||
TagBuilder tag;
|
||||
tag.AddItem(tag_type, pair->value);
|
||||
if (group == TAG_NUM_OF_ITEM_TYPES) {
|
||||
auto &values = result[std::string()];
|
||||
|
||||
if (tag.IsEmpty())
|
||||
/* if no tag item has been added, then the
|
||||
given value was not acceptable
|
||||
(e.g. empty); forcefully insert an empty
|
||||
tag in this case, as the caller expects the
|
||||
given tag type to be present */
|
||||
tag.AddEmptyItem(tag_type);
|
||||
while (auto *pair = mpd_recv_pair(connection)) {
|
||||
AtScopeExit(this, pair) {
|
||||
mpd_return_pair(connection, pair);
|
||||
};
|
||||
|
||||
try {
|
||||
visit_tag(tag.Commit());
|
||||
} catch (...) {
|
||||
mpd_response_finish(connection);
|
||||
throw;
|
||||
const auto current_type = tag_name_parse_i(pair->name);
|
||||
if (current_type == TAG_NUM_OF_ITEM_TYPES)
|
||||
continue;
|
||||
|
||||
if (current_type == tag_type)
|
||||
values.emplace(pair->value);
|
||||
}
|
||||
} else {
|
||||
std::set<std::string> *current_group = nullptr;
|
||||
|
||||
while (auto *pair = mpd_recv_pair(connection)) {
|
||||
AtScopeExit(this, pair) {
|
||||
mpd_return_pair(connection, pair);
|
||||
};
|
||||
|
||||
const auto current_type = tag_name_parse_i(pair->name);
|
||||
if (current_type == TAG_NUM_OF_ITEM_TYPES)
|
||||
continue;
|
||||
|
||||
if (current_type == tag_type) {
|
||||
if (current_group == nullptr)
|
||||
current_group = &result[std::string()];
|
||||
|
||||
current_group->emplace(pair->value);
|
||||
} else if (current_type == group) {
|
||||
current_group = &result[pair->value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mpd_response_finish(connection))
|
||||
ThrowError(connection);
|
||||
|
||||
return result;
|
||||
} catch (...) {
|
||||
if (connection != nullptr)
|
||||
mpd_search_cancel(connection);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
|
@@ -109,7 +109,7 @@ Directory::PruneEmpty() noexcept
|
||||
child != end;) {
|
||||
child->PruneEmpty();
|
||||
|
||||
if (child->IsEmpty())
|
||||
if (child->IsEmpty() && !child->IsMount())
|
||||
child = children.erase_and_dispose(child,
|
||||
DeleteDisposer());
|
||||
else
|
||||
@@ -230,7 +230,7 @@ Directory::Walk(bool recursive, const SongFilter *filter,
|
||||
call will lock it again */
|
||||
const ScopeDatabaseUnlock unlock;
|
||||
WalkMount(GetPath(), *mounted_database,
|
||||
recursive, filter,
|
||||
"", recursive, filter,
|
||||
visit_directory, visit_song,
|
||||
visit_playlist);
|
||||
return;
|
||||
|
@@ -127,7 +127,6 @@ public:
|
||||
*
|
||||
* @param name_utf8 the UTF-8 encoded name of the new sub directory
|
||||
*/
|
||||
gcc_malloc
|
||||
Directory *CreateChild(const char *name_utf8);
|
||||
|
||||
/**
|
||||
@@ -187,7 +186,7 @@ public:
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
const char *GetPath() const {
|
||||
const char *GetPath() const noexcept {
|
||||
return path.c_str();
|
||||
}
|
||||
|
||||
|
@@ -82,10 +82,11 @@ directory_save(BufferedOutputStream &os, const Directory &directory)
|
||||
}
|
||||
|
||||
for (const auto &child : directory.children) {
|
||||
os.Format(DIRECTORY_DIR "%s\n", child.GetName());
|
||||
if (child.IsMount())
|
||||
continue;
|
||||
|
||||
if (!child.IsMount())
|
||||
directory_save(os, child);
|
||||
os.Format(DIRECTORY_DIR "%s\n", child.GetName());
|
||||
directory_save(os, child);
|
||||
}
|
||||
|
||||
for (const auto &song : directory.songs)
|
||||
|
@@ -20,18 +20,12 @@
|
||||
#include "config.h"
|
||||
#include "Mount.hxx"
|
||||
#include "PrefixedLightSong.hxx"
|
||||
#include "SongFilter.hxx"
|
||||
#include "db/Selection.hxx"
|
||||
#include "db/LightDirectory.hxx"
|
||||
#include "db/Interface.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
/* workaround for "error: incomplete type 'PlaylistInfo' used in type
|
||||
trait expression" with libc++ version 3900 (from Android NDK
|
||||
r13b) */
|
||||
#include "db/PlaylistInfo.hxx"
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
struct PrefixedLightDirectory : LightDirectory {
|
||||
@@ -72,7 +66,7 @@ PrefixVisitPlaylist(const char *base, const VisitPlaylist &visit_playlist,
|
||||
|
||||
void
|
||||
WalkMount(const char *base, const Database &db,
|
||||
bool recursive, const SongFilter *filter,
|
||||
const char* uri, bool recursive, const SongFilter *filter,
|
||||
const VisitDirectory &visit_directory, const VisitSong &visit_song,
|
||||
const VisitPlaylist &visit_playlist)
|
||||
{
|
||||
@@ -93,5 +87,16 @@ WalkMount(const char *base, const Database &db,
|
||||
vp = std::bind(PrefixVisitPlaylist,
|
||||
base, std::ref(visit_playlist), _1, _2);
|
||||
|
||||
db.Visit(DatabaseSelection("", recursive, filter), vd, vs, vp);
|
||||
SongFilter prefix_filter;
|
||||
|
||||
if (base != nullptr && filter != nullptr) {
|
||||
/* if the SongFilter contains a LOCATE_TAG_BASE_TYPE
|
||||
item, copy the SongFilter and drop the mount point
|
||||
from the filter, because the mounted database
|
||||
doesn't know its own location within MPD's VFS */
|
||||
prefix_filter = filter->WithoutBasePrefix(base);
|
||||
filter = &prefix_filter;
|
||||
}
|
||||
|
||||
db.Visit(DatabaseSelection(uri, recursive, filter), vd, vs, vp);
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ class SongFilter;
|
||||
|
||||
void
|
||||
WalkMount(const char *base, const Database &db,
|
||||
bool recursive, const SongFilter *filter,
|
||||
const char* uri, bool recursive, const SongFilter *filter,
|
||||
const VisitDirectory &visit_directory, const VisitSong &visit_song,
|
||||
const VisitPlaylist &visit_playlist);
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "SimpleDatabasePlugin.hxx"
|
||||
#include "PrefixedLightSong.hxx"
|
||||
#include "Mount.hxx"
|
||||
#include "db/DatabasePlugin.hxx"
|
||||
#include "db/Selection.hxx"
|
||||
#include "db/Helpers.hxx"
|
||||
@@ -115,7 +116,7 @@ SimpleDatabase::Check() const
|
||||
path_utf8 + "\" because the "
|
||||
"parent path is not a directory");
|
||||
|
||||
#ifndef WIN32
|
||||
#ifndef _WIN32
|
||||
/* Check if we can write to the directory */
|
||||
if (!CheckAccess(dirPath, X_OK | W_OK)) {
|
||||
const int e = errno;
|
||||
@@ -134,7 +135,7 @@ SimpleDatabase::Check() const
|
||||
if (!fi.IsRegular())
|
||||
throw std::runtime_error("db file \"" + path_utf8 + "\" is not a regular file");
|
||||
|
||||
#ifndef WIN32
|
||||
#ifndef _WIN32
|
||||
/* And check that we can write to it */
|
||||
if (!CheckAccess(path, R_OK | W_OK))
|
||||
throw FormatErrno("Can't open db file \"%s\" for reading/writing",
|
||||
@@ -270,6 +271,18 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
|
||||
ScopeDatabaseLock protect;
|
||||
|
||||
auto r = root->LookupDirectory(selection.uri.c_str());
|
||||
|
||||
if (r.directory->IsMount()) {
|
||||
/* pass the request and the remaining uri to the mounted database */
|
||||
protect.unlock();
|
||||
|
||||
WalkMount(r.directory->GetPath(), *(r.directory->mounted_database),
|
||||
(r.uri == nullptr)?"":r.uri, selection.recursive, selection.filter,
|
||||
visit_directory, visit_song, visit_playlist);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.uri == nullptr) {
|
||||
/* it's a directory */
|
||||
|
||||
@@ -299,12 +312,11 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
|
||||
"No such directory");
|
||||
}
|
||||
|
||||
void
|
||||
SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const
|
||||
std::map<std::string, std::set<std::string>>
|
||||
SimpleDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group) const
|
||||
{
|
||||
::VisitUniqueTags(*this, selection, tag_type, group_mask, visit_tag);
|
||||
return ::CollectUniqueTags(*this, selection, tag_type, group);
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
|
@@ -76,7 +76,7 @@ public:
|
||||
const ConfigBlock &block);
|
||||
|
||||
gcc_pure
|
||||
Directory &GetRoot() {
|
||||
Directory &GetRoot() noexcept {
|
||||
assert(root != NULL);
|
||||
|
||||
return *root;
|
||||
@@ -119,13 +119,13 @@ public:
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const override;
|
||||
|
||||
void VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const override;
|
||||
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
TagType group) const override;
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
time_t GetUpdateStamp() const override {
|
||||
time_t GetUpdateStamp() const noexcept override {
|
||||
return mtime;
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -47,10 +48,6 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
|
||||
unsigned &didreadp,
|
||||
unsigned &totalp) const
|
||||
{
|
||||
// Create request
|
||||
char ofbuf[100], cntbuf[100];
|
||||
sprintf(ofbuf, "%u", offset);
|
||||
sprintf(cntbuf, "%u", count);
|
||||
// Some devices require an empty SortCriteria, else bad params
|
||||
IXML_Document *request =
|
||||
MakeActionHelper("Browse", m_serviceType.c_str(),
|
||||
@@ -58,8 +55,10 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
|
||||
"BrowseFlag", "BrowseDirectChildren",
|
||||
"Filter", "*",
|
||||
"SortCriteria", "",
|
||||
"StartingIndex", ofbuf,
|
||||
"RequestedCount", cntbuf);
|
||||
"StartingIndex",
|
||||
StringFormat<32>("%u", offset).c_str(),
|
||||
"RequestedCount",
|
||||
StringFormat<32>("%u", count).c_str());
|
||||
if (request == nullptr)
|
||||
throw std::runtime_error("UpnpMakeAction() failed");
|
||||
|
||||
@@ -112,15 +111,13 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
|
||||
unsigned offset = 0, total = -1, count;
|
||||
|
||||
do {
|
||||
char ofbuf[100];
|
||||
sprintf(ofbuf, "%d", offset);
|
||||
|
||||
UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
|
||||
"ContainerID", objectId,
|
||||
"SearchCriteria", ss,
|
||||
"Filter", "*",
|
||||
"SortCriteria", "",
|
||||
"StartingIndex", ofbuf,
|
||||
"StartingIndex",
|
||||
StringFormat<32>("%u", offset).c_str(),
|
||||
"RequestedCount", "0")); // Setting a value here gets twonky into fits
|
||||
if (!request)
|
||||
throw std::runtime_error("UpnpMakeAction() failed");
|
||||
|
@@ -60,7 +60,7 @@ public:
|
||||
* Parent's ObjectId
|
||||
*/
|
||||
std::string parent_id;
|
||||
|
||||
|
||||
std::string url;
|
||||
|
||||
/**
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
|
||||
UPnPDirObject &operator=(UPnPDirObject &&) = default;
|
||||
|
||||
void Clear() {
|
||||
void Clear() noexcept {
|
||||
id.clear();
|
||||
parent_id.clear();
|
||||
url.clear();
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool Check() const {
|
||||
bool Check() const noexcept {
|
||||
return !id.empty() && !parent_id.empty() && !name.empty() &&
|
||||
(type != UPnPDirObject::Type::ITEM ||
|
||||
item_class != UPnPDirObject::ItemClass::UNKNOWN);
|
||||
|
@@ -87,13 +87,13 @@ public:
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const override;
|
||||
|
||||
void VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const override;
|
||||
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
TagType group) const override;
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
time_t GetUpdateStamp() const override {
|
||||
time_t GetUpdateStamp() const noexcept override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -603,17 +603,15 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
|
||||
visit_directory, visit_song, visit_playlist);
|
||||
}
|
||||
|
||||
void
|
||||
UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag, gcc_unused tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const
|
||||
std::map<std::string, std::set<std::string>>
|
||||
UpnpDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag, TagType group) const
|
||||
{
|
||||
// TODO: use group_mask
|
||||
(void)group; // TODO: use group
|
||||
|
||||
if (!visit_tag)
|
||||
return;
|
||||
std::map<std::string, std::set<std::string>> result;
|
||||
auto &values = result[std::string()];
|
||||
|
||||
std::set<std::string> values;
|
||||
for (auto& server : discovery->GetDirectories()) {
|
||||
const auto dirbuf = SearchSongs(server, rootid, selection);
|
||||
|
||||
@@ -633,11 +631,7 @@ UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& value : values) {
|
||||
TagBuilder builder;
|
||||
builder.AddItem(tag, value.c_str());
|
||||
visit_tag(builder.Commit());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
|
@@ -26,8 +26,8 @@
|
||||
#include "ExcludeList.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/NarrowPath.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "input/TextInputStream.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
@@ -35,39 +35,33 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
inline void
|
||||
ExcludeList::ParseLine(char *line) noexcept
|
||||
{
|
||||
char *p = Strip(line);
|
||||
if (*p != 0 && *p != '#')
|
||||
patterns.emplace_front(p);
|
||||
}
|
||||
|
||||
bool
|
||||
ExcludeList::LoadFile(Path path_fs)
|
||||
try {
|
||||
ExcludeList::Load(InputStreamPtr is)
|
||||
{
|
||||
#ifdef HAVE_CLASS_GLOB
|
||||
TextFile file(path_fs);
|
||||
TextInputStream tis(std::move(is));
|
||||
|
||||
char *line;
|
||||
while ((line = file.ReadLine()) != nullptr) {
|
||||
char *p = strchr(line, '#');
|
||||
if (p != nullptr)
|
||||
*p = 0;
|
||||
|
||||
p = Strip(line);
|
||||
if (*p != 0)
|
||||
patterns.emplace_front(p);
|
||||
}
|
||||
while ((line = tis.ReadLine()) != nullptr)
|
||||
ParseLine(line);
|
||||
#else
|
||||
/* not implemented */
|
||||
(void)path_fs;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
} catch (const std::system_error &e) {
|
||||
if (!IsFileNotFound(e))
|
||||
LogError(e);
|
||||
return false;
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ExcludeList::Check(Path name_fs) const
|
||||
ExcludeList::Check(Path name_fs) const noexcept
|
||||
{
|
||||
assert(!name_fs.IsNull());
|
||||
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "check.h"
|
||||
#include "Compiler.h"
|
||||
#include "fs/Glob.hxx"
|
||||
#include "input/Ptr.hxx"
|
||||
|
||||
#ifdef HAVE_CLASS_GLOB
|
||||
#include <forward_list>
|
||||
@@ -50,7 +51,7 @@ public:
|
||||
:parent(&_parent) {}
|
||||
|
||||
gcc_pure
|
||||
bool IsEmpty() const {
|
||||
bool IsEmpty() const noexcept {
|
||||
#ifdef HAVE_CLASS_GLOB
|
||||
return ((parent == nullptr) || parent->IsEmpty()) && patterns.empty();
|
||||
#else
|
||||
@@ -62,13 +63,16 @@ public:
|
||||
/**
|
||||
* Loads and parses a .mpdignore file.
|
||||
*/
|
||||
bool LoadFile(Path path_fs);
|
||||
bool Load(InputStreamPtr is);
|
||||
|
||||
/**
|
||||
* Checks whether one of the patterns in the .mpdignore file matches
|
||||
* the specified file name.
|
||||
*/
|
||||
bool Check(Path name_fs) const;
|
||||
bool Check(Path name_fs) const noexcept;
|
||||
|
||||
private:
|
||||
void ParseLine(char *line) noexcept;
|
||||
};
|
||||
|
||||
|
||||
|
@@ -49,6 +49,10 @@ struct UpdateQueueItem {
|
||||
bool IsDefined() const {
|
||||
return id != 0;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
id = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class UpdateQueue {
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include "Idle.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "thread/Thread.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "thread/Util.hxx"
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -43,6 +44,7 @@ UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
|
||||
:DeferredMonitor(_loop),
|
||||
db(_db), storage(_storage),
|
||||
listener(_listener),
|
||||
update_thread(BIND_THIS_METHOD(Task)),
|
||||
update_task_id(0),
|
||||
walk(nullptr)
|
||||
{
|
||||
@@ -112,6 +114,8 @@ UpdateService::Task()
|
||||
{
|
||||
assert(walk != nullptr);
|
||||
|
||||
SetThreadName("update");
|
||||
|
||||
if (!next.path_utf8.empty())
|
||||
FormatDebug(update_domain, "starting: %s",
|
||||
next.path_utf8.c_str());
|
||||
@@ -140,13 +144,6 @@ UpdateService::Task()
|
||||
DeferredMonitor::Schedule();
|
||||
}
|
||||
|
||||
void
|
||||
UpdateService::Task(void *ctx)
|
||||
{
|
||||
UpdateService &service = *(UpdateService *)ctx;
|
||||
return service.Task();
|
||||
}
|
||||
|
||||
void
|
||||
UpdateService::StartThread(UpdateQueueItem &&i)
|
||||
{
|
||||
@@ -158,7 +155,7 @@ UpdateService::StartThread(UpdateQueueItem &&i)
|
||||
next = std::move(i);
|
||||
walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
|
||||
|
||||
update_thread.Start(Task, this);
|
||||
update_thread.Start();
|
||||
|
||||
FormatDebug(update_domain,
|
||||
"spawned thread for update job id %i", next.id);
|
||||
@@ -258,7 +255,7 @@ UpdateService::RunDeferred()
|
||||
delete walk;
|
||||
walk = nullptr;
|
||||
|
||||
next = UpdateQueueItem();
|
||||
next.Clear();
|
||||
|
||||
idle_add(IDLE_UPDATE);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user