Compare commits
419 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bf41d1ad2b | ||
![]() |
d27e534a85 | ||
![]() |
6d54928d7c | ||
![]() |
0dffe05bf7 | ||
![]() |
9ef1f10319 | ||
![]() |
23fcfdbd2a | ||
![]() |
3401d26d4c | ||
![]() |
256753ea46 | ||
![]() |
76cd5f8595 | ||
![]() |
5684025847 | ||
![]() |
744bd1eadc | ||
![]() |
2bc127bb43 | ||
![]() |
7770298a65 | ||
![]() |
fa50cdb39e | ||
![]() |
816ef12088 | ||
![]() |
5ff786e59c | ||
![]() |
80fe88e8f6 | ||
![]() |
a1afe9afc6 | ||
![]() |
fe598e7d30 | ||
![]() |
4475b8ca04 | ||
![]() |
a714bdb0ce | ||
![]() |
087874620f | ||
![]() |
f1116c9258 | ||
![]() |
d01fb6730a | ||
![]() |
7bfe6a3304 | ||
![]() |
9a577f8060 | ||
![]() |
d75a0d714e | ||
![]() |
9be3a1554e | ||
![]() |
7764719513 | ||
![]() |
dcbb9fe07c | ||
![]() |
e3b347820a | ||
![]() |
a84bf5a92e | ||
![]() |
732bdc800d | ||
![]() |
a8661b5931 | ||
![]() |
5680a3a4b7 | ||
![]() |
15ce8eb487 | ||
![]() |
b7744be208 | ||
![]() |
63c5d66016 | ||
![]() |
d09bd9178f | ||
![]() |
7d8b1860c3 | ||
![]() |
b06825829b | ||
![]() |
ba4cd47fd8 | ||
![]() |
bbe403f141 | ||
![]() |
5df2707d98 | ||
![]() |
4859ea468f | ||
![]() |
2a8830db70 | ||
![]() |
fed9b6fd74 | ||
![]() |
b02890eb8a | ||
![]() |
da882a6eb6 | ||
![]() |
aeb89aa9d6 | ||
![]() |
f885807ecc | ||
![]() |
b826fd71f0 | ||
![]() |
ae35df1126 | ||
![]() |
80e55f6bfc | ||
![]() |
e7411c0c4b | ||
![]() |
e9af692973 | ||
![]() |
0cf90ee8b6 | ||
![]() |
dc3c0c8866 | ||
![]() |
1c46bb1ba6 | ||
![]() |
2e8f42c6ad | ||
![]() |
2b301ffd2c | ||
![]() |
ef0765ca10 | ||
![]() |
9766ac6db3 | ||
![]() |
32799ff682 | ||
![]() |
ce093be12c | ||
![]() |
2c276770f0 | ||
![]() |
75a592f629 | ||
![]() |
13ce07d181 | ||
![]() |
d659c7df19 | ||
![]() |
f8403a1d29 | ||
![]() |
ebb952c4ad | ||
![]() |
bea3b954a5 | ||
![]() |
129d8e89b9 | ||
![]() |
65778a3774 | ||
![]() |
d9841668ff | ||
![]() |
85d27cbcb9 | ||
![]() |
9b95e65bd9 | ||
![]() |
12a86c4975 | ||
![]() |
0b9435858b | ||
![]() |
f0386459ee | ||
![]() |
e98d4670b8 | ||
![]() |
56cc42b752 | ||
![]() |
ead208987d | ||
![]() |
364acc8949 | ||
![]() |
a8f4d2b6fc | ||
![]() |
0eb113e7c6 | ||
![]() |
96a9670c69 | ||
![]() |
dcc5ce6792 | ||
![]() |
23d08820a2 | ||
![]() |
b9b906ab20 | ||
![]() |
964804a4c2 | ||
![]() |
92495d2b0b | ||
![]() |
9270829b5b | ||
![]() |
b6243a9945 | ||
![]() |
496f88653d | ||
![]() |
5ef645df97 | ||
![]() |
bf49c9e4e2 | ||
![]() |
0da9c91af2 | ||
![]() |
193e637dd9 | ||
![]() |
928bee933d | ||
![]() |
4d1720c886 | ||
![]() |
8f8ed87327 | ||
![]() |
28a441c977 | ||
![]() |
8cf50b08f2 | ||
![]() |
818b7e0641 | ||
![]() |
e70f40fac1 | ||
![]() |
bc89ca92b4 | ||
![]() |
b968e1b6de | ||
![]() |
6c9f9c136b | ||
![]() |
9bff5f9e36 | ||
![]() |
2bf26a2ff8 | ||
![]() |
e33b50d9c5 | ||
![]() |
21fa44c0d5 | ||
![]() |
44444e1b89 | ||
![]() |
ca450663d0 | ||
![]() |
f3d16f6d1b | ||
![]() |
4464cdcc67 | ||
![]() |
2d61e526de | ||
![]() |
7723c481db | ||
![]() |
0ed10542cc | ||
![]() |
ab830f9afd | ||
![]() |
d4d2bc072e | ||
![]() |
bcccc8f66c | ||
![]() |
848c63e2d5 | ||
![]() |
f6d0310f9c | ||
![]() |
3ef043392c | ||
![]() |
864d6f312d | ||
![]() |
f44c67de09 | ||
![]() |
ae19bda1f2 | ||
![]() |
f2d8fd769d | ||
![]() |
9661062ae2 | ||
![]() |
2a07354cad | ||
![]() |
fc18fd571c | ||
![]() |
51abed9732 | ||
![]() |
d00afc912c | ||
![]() |
9d0fe725eb | ||
![]() |
8a432c9b7f | ||
![]() |
187204f03c | ||
![]() |
5e5fadb5f2 | ||
![]() |
952c793235 | ||
![]() |
3e3d8c7f9d | ||
![]() |
9b99a9897a | ||
![]() |
4f56fdc397 | ||
![]() |
c87d6825ec | ||
![]() |
00830a20e3 | ||
![]() |
d39d2874b4 | ||
![]() |
a0a74951b8 | ||
![]() |
779a6855ff | ||
![]() |
f7ed7446ae | ||
![]() |
9d44a6d2ae | ||
![]() |
10da9ee7ba | ||
![]() |
f9eff31205 | ||
![]() |
1d74a029a2 | ||
![]() |
6b8ca514bb | ||
![]() |
f51e555154 | ||
![]() |
61a3c69a06 | ||
![]() |
089615a01e | ||
![]() |
52bee8f81f | ||
![]() |
adc25e648f | ||
![]() |
31da8eac9b | ||
![]() |
e00464435b | ||
![]() |
b81138bda1 | ||
![]() |
6de088140b | ||
![]() |
86d0534638 | ||
![]() |
1033dbca2b | ||
![]() |
b955334882 | ||
![]() |
90ea3bf985 | ||
![]() |
83b0871248 | ||
![]() |
d8aec4b2dc | ||
![]() |
39b302dcad | ||
![]() |
f6125f0c35 | ||
![]() |
f780ac418a | ||
![]() |
61a72a5d13 | ||
![]() |
0c0a354753 | ||
![]() |
3c5f860fb8 | ||
![]() |
3da1fa88d0 | ||
![]() |
fac15aaffb | ||
![]() |
c926021599 | ||
![]() |
543776d9c9 | ||
![]() |
8bf3f9b874 | ||
![]() |
f07f8f7d88 | ||
![]() |
39b40ac1fd | ||
![]() |
ea639269d8 | ||
![]() |
0abaa3ecc5 | ||
![]() |
c4d3efe71d | ||
![]() |
85e82e3d4d | ||
![]() |
f44011519c | ||
![]() |
2c3eeb7194 | ||
![]() |
79839db3a3 | ||
![]() |
d478bdda8e | ||
![]() |
1eae9339f2 | ||
![]() |
923c1b6220 | ||
![]() |
09884e608b | ||
![]() |
e239009295 | ||
![]() |
3fae2150f5 | ||
![]() |
f9ca2f52c1 | ||
![]() |
4b81cf0c2c | ||
![]() |
e7acbf112c | ||
![]() |
304d45b551 | ||
![]() |
0f488dcecf | ||
![]() |
17039aec70 | ||
![]() |
fb6cb07912 | ||
![]() |
e9e0e02db3 | ||
![]() |
03507037e8 | ||
![]() |
66a8fac25e | ||
![]() |
1b902e00b4 | ||
![]() |
923e66738c | ||
![]() |
ff3e2c0514 | ||
![]() |
6922a2f55e | ||
![]() |
ca5a400dbe | ||
![]() |
63fe4d1d17 | ||
![]() |
ca06d9d3bf | ||
![]() |
ed2db04f43 | ||
![]() |
de0afa0e08 | ||
![]() |
f0d3227d7b | ||
![]() |
fb07a7cecc | ||
![]() |
c6b08a4d48 | ||
![]() |
040e87ad8d | ||
![]() |
d5521ead56 | ||
![]() |
f8468451c9 | ||
![]() |
65df6ca14e | ||
![]() |
36dec47bf7 | ||
![]() |
478cedcadf | ||
![]() |
cabcbb059d | ||
![]() |
5e21b2db3c | ||
![]() |
3a0d6d96c1 | ||
![]() |
f39d2d33c0 | ||
![]() |
ead3dc6a92 | ||
![]() |
7d814cc899 | ||
![]() |
f5b4606c09 | ||
![]() |
d6dbf64efb | ||
![]() |
8d18b4c24b | ||
![]() |
fe8621906d | ||
![]() |
b4fcbdb235 | ||
![]() |
f4b5a28596 | ||
![]() |
6cbd77fc57 | ||
![]() |
1bc78e9f2c | ||
![]() |
cb6282e0a7 | ||
![]() |
f6941f9a44 | ||
![]() |
d2eb4df8fc | ||
![]() |
df33a898d7 | ||
![]() |
325c7b8e8b | ||
![]() |
380656d8c9 | ||
![]() |
9111bc2c21 | ||
![]() |
37b54179d8 | ||
![]() |
511826763a | ||
![]() |
ef10354d06 | ||
![]() |
158458db5f | ||
![]() |
e183ab5cf8 | ||
![]() |
fef839e2a9 | ||
![]() |
9776e43bbe | ||
![]() |
5201147ab1 | ||
![]() |
fb7daa0d05 | ||
![]() |
2e9f3d8b9f | ||
![]() |
976731ab6c | ||
![]() |
0d8942e64a | ||
![]() |
37a0f04712 | ||
![]() |
cde9348009 | ||
![]() |
095e6e6ad4 | ||
![]() |
9d0bf5e95c | ||
![]() |
8b327f1d9b | ||
![]() |
aef0507abb | ||
![]() |
6bab3bcfea | ||
![]() |
a854595886 | ||
![]() |
8fc3c5c612 | ||
![]() |
4f408bd952 | ||
![]() |
7de8fd04a4 | ||
![]() |
8158bd218c | ||
![]() |
aa1d867b72 | ||
![]() |
34c8242133 | ||
![]() |
e22bdee808 | ||
![]() |
7f87de783f | ||
![]() |
c66389a453 | ||
![]() |
b63c1a2144 | ||
![]() |
808dd7cc54 | ||
![]() |
62a129c18f | ||
![]() |
c18cd941aa | ||
![]() |
6d12c22653 | ||
![]() |
b76d78e6ae | ||
![]() |
0a6e484b1a | ||
![]() |
0bb71f1f20 | ||
![]() |
1aa7cdd602 | ||
![]() |
a4b8a0d801 | ||
![]() |
3bf521d5ca | ||
![]() |
0acb55cde5 | ||
![]() |
6b89fd6100 | ||
![]() |
52ce39dc3e | ||
![]() |
7a3e15d8e5 | ||
![]() |
cf66a60c60 | ||
![]() |
9b26d451e4 | ||
![]() |
137ffba1b4 | ||
![]() |
5c5dc1b7c0 | ||
![]() |
9e9418294a | ||
![]() |
b850eb74b7 | ||
![]() |
67d73a2aee | ||
![]() |
fde9a470dd | ||
![]() |
8d1f30e55b | ||
![]() |
ddd2b60489 | ||
![]() |
8777737861 | ||
![]() |
cb71f6dd04 | ||
![]() |
1881b0e975 | ||
![]() |
98b29f6d1c | ||
![]() |
59fdfd25cb | ||
![]() |
0d98677212 | ||
![]() |
38f0c16904 | ||
![]() |
4fbf6b6c95 | ||
![]() |
1f8ff48168 | ||
![]() |
20b6e0d684 | ||
![]() |
713c1f2ba9 | ||
![]() |
a149bc4c5d | ||
![]() |
b3a458338a | ||
![]() |
44422b2b2f | ||
![]() |
f10afd38b5 | ||
![]() |
4c50a5e0b3 | ||
![]() |
f255a485b7 | ||
![]() |
1930d5774d | ||
![]() |
7220a76be0 | ||
![]() |
83f7610dd1 | ||
![]() |
30e0644722 | ||
![]() |
3ada464020 | ||
![]() |
d5983dd362 | ||
![]() |
98258acc37 | ||
![]() |
8002bc752f | ||
![]() |
834ad7a58f | ||
![]() |
e8f2f98048 | ||
![]() |
c672b60d07 | ||
![]() |
ea269c9c92 | ||
![]() |
1fe3a77640 | ||
![]() |
bbaeea1ab7 | ||
![]() |
0a3aee9d82 | ||
![]() |
2434020971 | ||
![]() |
41e0eb7378 | ||
![]() |
6adf964c81 | ||
![]() |
b59f37bc0a | ||
![]() |
cf2d171ccc | ||
![]() |
cc28a7b67f | ||
![]() |
8b5c33cecd | ||
![]() |
6c28adbcd2 | ||
![]() |
2125e3ed57 | ||
![]() |
3da7ecfadf | ||
![]() |
5bb02bbd39 | ||
![]() |
f11aa09f7c | ||
![]() |
02eb4752d3 | ||
![]() |
d9c3215584 | ||
![]() |
110e6d026b | ||
![]() |
c0f57b8a8b | ||
![]() |
57633fbcb3 | ||
![]() |
864c87e6c0 | ||
![]() |
1a516cf3c0 | ||
![]() |
5c25499c5e | ||
![]() |
da4bb4c298 | ||
![]() |
5b8ff61799 | ||
![]() |
56bded07b1 | ||
![]() |
db144a43ad | ||
![]() |
5965f62b56 | ||
![]() |
05aa9f72a9 | ||
![]() |
281461f0f0 | ||
![]() |
f70eb63879 | ||
![]() |
99c23cf139 | ||
![]() |
9aa75e738c | ||
![]() |
e9c45a9140 | ||
![]() |
a065c6e6b9 | ||
![]() |
feb5ff9bd2 | ||
![]() |
92ec3f0881 | ||
![]() |
98c47d9d36 | ||
![]() |
6c67408944 | ||
![]() |
261a816b21 | ||
![]() |
7a23c123c8 | ||
![]() |
e85b24bee0 | ||
![]() |
9e73ea77b4 | ||
![]() |
b0739eca87 | ||
![]() |
848f6aa5ab | ||
![]() |
c9ba4f3f9c | ||
![]() |
c0e9246a66 | ||
![]() |
096c23f27d | ||
![]() |
40bde1eac9 | ||
![]() |
4b55ed17a9 | ||
![]() |
4f757a5add | ||
![]() |
674c137e5f | ||
![]() |
ff1ff1e54a | ||
![]() |
42b22187c8 | ||
![]() |
cfe22502ab | ||
![]() |
d77b0c7dcd | ||
![]() |
5cf889b676 | ||
![]() |
ffc36d5255 | ||
![]() |
0126276e2f | ||
![]() |
58d6ddab9e | ||
![]() |
05db6934eb | ||
![]() |
02c68c5cdb | ||
![]() |
b02fee7309 | ||
![]() |
424f75c9e1 | ||
![]() |
f6e1176f97 | ||
![]() |
e4700c0a27 | ||
![]() |
cf23fd8774 | ||
![]() |
dee8872395 | ||
![]() |
4ba9357a9c | ||
![]() |
48ec09ab1e | ||
![]() |
754f4048a8 | ||
![]() |
037bb07d08 | ||
![]() |
87635c5268 | ||
![]() |
528b4338f4 | ||
![]() |
c780b8bba9 | ||
![]() |
ca34f3250b | ||
![]() |
6a68e1c3f3 | ||
![]() |
85f77ec81d | ||
![]() |
37debed0b8 | ||
![]() |
008383f24a | ||
![]() |
4f7d52dbf2 | ||
![]() |
c7848da8f2 | ||
![]() |
10a6c5c57d | ||
![]() |
2cc2bab309 | ||
![]() |
701fd1d939 | ||
![]() |
d1bdea8edb | ||
![]() |
0cea67ee70 | ||
![]() |
3a0480a482 | ||
![]() |
1fa99da3c2 | ||
![]() |
22d669da18 | ||
![]() |
772681f23d | ||
![]() |
1862a98a44 |
.travis.ymlNEWS
android
build
doc
meson.buildmeson_options.txtpython/build
src
CommandLine.cxxInstance.cxxInstance.hxxLocateUri.cxxLocateUri.hxxLogInit.cxxLogInit.hxxMain.cxxMapper.cxxMapper.hxxMusicChunk.hxxPlaylistDatabase.cxxPlaylistFile.cxxReplayGainInfo.hxxSongLoader.cxxSongPrint.cxxSongSave.cxxSongUpdate.cxxStateFile.cxxStats.cxxTagPrint.cxxTimePrint.cxx
archive
client
command
AllCommands.cxxAllCommands.hxxDatabaseCommands.cxxFileCommands.cxxOtherCommands.cxxPlaylistCommands.cxxQueueCommands.cxxStorageCommands.cxx
config
db
Configured.cxxConfigured.hxxDatabaseGlue.cxxDatabaseGlue.hxxDatabasePlugin.hxxDatabasePrint.cxxDatabasePrint.hxxInterface.hxxPtr.hxxUniqueTags.cxxUniqueTags.hxxVHelper.cxxVHelper.hxxmeson.build
plugins
update
decoder
event
BufferedSocket.cxxBufferedSocket.hxxCall.cxxFullyBufferedSocket.hxxLoop.cxxLoop.hxxMultiSocketMonitor.cxxMultiSocketMonitor.hxxServerSocket.cxxServerSocket.hxxSocketMonitor.cxxSocketMonitor.hxxThread.cxx
filter
fs
io
haiku
input
lib
cdio
curl
Easy.hxxEscape.cxxEscape.hxxForm.cxxGlobal.cxxGlobal.hxxHandler.hxxInit.cxxMulti.hxxRequest.cxxRequest.hxxSlist.hxxString.hxxVersion.cxxVersion.hxxmeson.build
patches
dbus
gcrypt
icu
nfs
sqlite
xiph
mixer
plugins
neighbor
net
AllocatedSocketAddress.cxxAllocatedSocketAddress.hxxHostParser.cxxHostParser.hxxIPv4Address.cxxIPv4Address.hxxIPv6Address.cxxIPv6Address.hxxSocketAddress.cxxSocketAddress.hxxSocketDescriptor.cxxSocketDescriptor.hxxSocketUtil.cxxSocketUtil.hxxStaticSocketAddress.cxxStaticSocketAddress.hxxToString.cxx
output
Thread.cxx
plugins
pcm
player
playlist
PlaylistAny.cxxPlaylistAny.hxxPlaylistQueue.cxxPlaylistQueue.hxxPlaylistRegistry.hxxPlaylistSong.cxxPrint.cxxPrint.hxx
plugins
queue
song
Filter.cxxModifiedSinceSongFilter.cxxStringFilter.cxxStringFilter.hxxTagSongFilter.cxxTagSongFilter.hxxmeson.build
storage
system
tag
thread
time
unix
util
Cast.hxxCharUtil.hxxCompiler.hOffsetPointer.hxxPrintException.cxxPrintException.hxxRecursiveMap.hxxStaticFifoBuffer.hxxStringBuffer.hxxStringFormat.hxxStringView.hxxWStringView.hxxformat.cmeson.build
zeroconf
systemd
test
ConfigGlue.hxxContainerScan.cxxDumpDatabase.cxxMakeTag.hxxRunChromaprint.cxxRunCurl.cxxTestISO8601.cxxTestTagSongFilter.cxxdump_playlist.cxxdump_text_file.cxxmeson.build
valgrind.suppressionsnet
read_tags.cxxrun_decoder.cxxrun_filter.cxxrun_input.cxxrun_neighbor_explorer.cxxrun_output.cxxrun_storage.cxxtest_translate_song.cxxvisit_archive.cxxwin32/res
57
.travis.yml
57
.travis.yml
@@ -2,6 +2,28 @@ language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Ubuntu Bionic (18.04) with GCC 7
|
||||
- os: linux
|
||||
dist: bionic
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- libgtest-dev
|
||||
- libboost-dev
|
||||
- python3.6
|
||||
- python3-urllib3
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
# Ubuntu Trusty (16.04) with GCC 6
|
||||
- os: linux
|
||||
dist: trusty
|
||||
addons:
|
||||
@@ -9,7 +31,7 @@ matrix:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- sourceline: 'ppa:mhier/libboost-latest'
|
||||
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
|
||||
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- g++-6
|
||||
@@ -25,8 +47,9 @@ matrix:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
env:
|
||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||
- MATRIX_EVAL="export CC=gcc-6 CXX=g++-6 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
||||
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
# Ubuntu Trusty (16.04) with GCC 8
|
||||
- os: linux
|
||||
dist: trusty
|
||||
addons:
|
||||
@@ -34,7 +57,7 @@ matrix:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- sourceline: 'ppa:mhier/libboost-latest'
|
||||
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
|
||||
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- g++-8
|
||||
@@ -50,25 +73,37 @@ matrix:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
env:
|
||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||
- MATRIX_EVAL="export CC=gcc-8 CXX=g++-8 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
||||
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
- os: osx
|
||||
osx_image: xcode9.3beta
|
||||
osx_image: xcode9.4
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- ccache
|
||||
- meson
|
||||
env:
|
||||
- MATRIX_EVAL=""
|
||||
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
||||
|
||||
cache:
|
||||
- apt
|
||||
- ccache
|
||||
apt: true
|
||||
ccache: true
|
||||
directories:
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
|
||||
before_cache:
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew cleanup
|
||||
|
||||
before_install:
|
||||
- eval "${MATRIX_EVAL}"
|
||||
# C++14
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew update
|
||||
|
||||
install:
|
||||
# C++14
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install ccache meson
|
||||
|
||||
# Work around "Target /usr/local/lib/libgtest.a is a symlink
|
||||
# belonging to nss. You can unlink it" during gtest install
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
|
||||
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
||||
|
||||
before_script:
|
||||
|
166
NEWS
166
NEWS
@@ -1,3 +1,169 @@
|
||||
ver 0.21.18 (2019/12/24)
|
||||
* protocol
|
||||
- work around Mac OS X bug in the ISO 8601 parser
|
||||
* output
|
||||
- alsa: fix hang bug with ALSA "null" outputs
|
||||
* storage
|
||||
- curl: fix crash bug
|
||||
* drop support for CURL versions older than 7.32.0
|
||||
* reduce unnecessary CPU wakeups
|
||||
|
||||
ver 0.21.17 (2019/12/16)
|
||||
* protocol
|
||||
- relax the ISO 8601 parser: allow omitting field separators, the
|
||||
time of day and the "Z" suffix
|
||||
* archive
|
||||
- zzip: improve error reporting
|
||||
* outputs
|
||||
- jack: mark ports as terminal
|
||||
- shout: declare metadata as UTF-8
|
||||
* fix build failure with -Ddatabase=false
|
||||
|
||||
ver 0.21.16 (2019/10/16)
|
||||
* queue
|
||||
- fix relative destination offset when moving a range
|
||||
* storage
|
||||
- curl: request the "resourcetype" property to fix database update
|
||||
- curl: URL-encode more paths
|
||||
- curl: follow redirects for collections without trailing slash
|
||||
* update
|
||||
- fix crash when music_directory is not a directory
|
||||
* fix build with iconv() instead of ICU
|
||||
|
||||
ver 0.21.15 (2019/09/25)
|
||||
* decoder
|
||||
- dsdiff, dsf: fix displayed bit rate
|
||||
- mpcdec: fix bogus ReplayGain values
|
||||
* output
|
||||
- solaris: fix build with glibc 2.30
|
||||
|
||||
ver 0.21.14 (2019/08/21)
|
||||
* decoder
|
||||
- sidplay: show track durations in database
|
||||
- sidplay: convert tag values from Windows-1252 charset
|
||||
- sidplay: strip text from "Date" tag
|
||||
* player
|
||||
- fix crash after song change
|
||||
- fix seek position after restarting the decoder
|
||||
* protocol
|
||||
- include command name in error responses
|
||||
|
||||
ver 0.21.13 (2019/08/06)
|
||||
* input
|
||||
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1
|
||||
* decoder
|
||||
- mad: fix crackling sound (0.21.12 regression)
|
||||
* output
|
||||
- jack: improved Windows compatibility
|
||||
|
||||
ver 0.21.12 (2019/08/03)
|
||||
* decoder
|
||||
- mad: update bit rate after seeking
|
||||
- mad: fix several bugs preventing the plugin from decoding the last frame
|
||||
- opus: ignore case in replay gain tag names
|
||||
- opus, vorbis: decode the "end of stream" packet
|
||||
* output
|
||||
- jack: fix mono-to-stereo conversion
|
||||
* player
|
||||
- don't restart unseekable song after failed seek attempt
|
||||
* Windows
|
||||
- support backslash in relative URIs loaded from playlists
|
||||
|
||||
ver 0.21.11 (2019/07/03)
|
||||
* input
|
||||
- tidal: deprecated because Tidal has changed the protocol
|
||||
* decoder
|
||||
- wildmidi: log error if library initialization fails
|
||||
* output
|
||||
- alsa: fix busy loop while draining
|
||||
- alsa: fix missing drain call
|
||||
- alsa: improve xrun-avoiding silence generator
|
||||
- alsa: log when generating silence due to slow decoder
|
||||
- alsa, osx: fix distortions with DSD_U32 and DoP on 32 bit CPUs
|
||||
* protocol
|
||||
- fix "list" with multiple "group" levels
|
||||
|
||||
ver 0.21.10 (2019/06/05)
|
||||
* decoder
|
||||
- opus: fix duplicate tags
|
||||
* output
|
||||
- httpd: reject some well-known URIs
|
||||
* fix crash bug (0.21.9 regression)
|
||||
|
||||
ver 0.21.9 (2019/05/20)
|
||||
* input
|
||||
- buffer: fix deadlock bug
|
||||
* Android
|
||||
- fix crash on ARMv7
|
||||
- request storage permission on Android 6+
|
||||
* fix spurious "single" mode bug
|
||||
|
||||
ver 0.21.8 (2019/04/23)
|
||||
* input
|
||||
- smbclient: download to buffer instead of throttling transfer
|
||||
* output
|
||||
- httpd: add missing mutex lock
|
||||
- httpd: fix use-after-free bug
|
||||
* playlist
|
||||
- soundcloud: fix "Unsupported URI scheme" (0.21.6 regression)
|
||||
* fix Bonjour bug
|
||||
* fix build failure with GCC 9
|
||||
* fix build failure with -Ddatabase=false
|
||||
* systemd: add user socket unit
|
||||
* doc: "list file" is deprecated
|
||||
|
||||
ver 0.21.7 (2019/04/03)
|
||||
* input
|
||||
- qobuz/tidal: scan tags when loading a playlist
|
||||
* require Meson 0.49.0 for native libgcrypt-config support
|
||||
* fix build failure with -Dlocal_socket=false
|
||||
* Haiku
|
||||
- fix build
|
||||
- add version info
|
||||
|
||||
ver 0.21.6 (2019/03/17)
|
||||
* protocol
|
||||
- allow loading playlists specified as absolute filesystem paths
|
||||
- fix negated filter expressions with multiple tag values
|
||||
- fix "list" with filter expression
|
||||
- omit empty playlist names in "listplaylists"
|
||||
* input
|
||||
- cdio_paranoia: fix build failure due to missing #include
|
||||
* decoder
|
||||
- opus: fix replay gain when there are no other tags
|
||||
- opus: fix seeking to beginning of song
|
||||
- vorbis: fix Tremor conflict resulting in crash
|
||||
* output
|
||||
- pulse: work around error with unusual channel count
|
||||
- osx: fix build failure
|
||||
* playlist
|
||||
- flac: fix use-after-free bug
|
||||
* support abstract sockets on Linux
|
||||
* Windows
|
||||
- remove the unused libwinpthread-1.dll dependency
|
||||
* Android
|
||||
- enable SLES power saving mode
|
||||
|
||||
ver 0.21.5 (2019/02/22)
|
||||
* protocol
|
||||
- fix deadlock in "albumart" command
|
||||
- fix "tagtypes disable" command
|
||||
* database
|
||||
- simple: fix assertion failure
|
||||
- fix assertion failures with mount points
|
||||
* storage
|
||||
- udisks: fix "AlreadyMounted" error
|
||||
- udisks: use relative path from mount URI
|
||||
- fix memory leak
|
||||
* input
|
||||
- buffer: fix crash bug when playing remote WAV file
|
||||
* tags
|
||||
- ape: map "Album Artist"
|
||||
* output
|
||||
- shout: add support for TLS
|
||||
* mixer
|
||||
- pulse: add "scale_volume" setting
|
||||
|
||||
ver 0.21.4 (2019/01/04)
|
||||
* database
|
||||
- inotify: fix crash bug "terminate called after throwing ..."
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="26"
|
||||
android:versionName="0.21.4">
|
||||
android:versionCode="41"
|
||||
android:versionName="0.21.18">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||
|
||||
|
@@ -138,6 +138,12 @@ class AndroidNdkToolchain:
|
||||
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
|
||||
libstdcxx_libs = '-lc++_static -lc++abi'
|
||||
|
||||
if self.is_armv7:
|
||||
# On 32 bit ARM, clang generates no ".eh_frame" section;
|
||||
# instead, the LLVM unwinder library is used for unwinding
|
||||
# the stack after a C++ exception was thrown
|
||||
libstdcxx_libs += ' -lunwind'
|
||||
|
||||
if use_cxx:
|
||||
self.cxxflags += ' ' + libstdcxx_cxxflags
|
||||
self.ldflags += ' ' + libstdcxx_ldflags
|
||||
|
@@ -6,7 +6,7 @@ android_sdk = get_option('android_sdk')
|
||||
android_abi = get_option('android_abi')
|
||||
|
||||
android_sdk_build_tools_version = '27.0.0'
|
||||
android_sdk_platform = 'android-21'
|
||||
android_sdk_platform = 'android-23'
|
||||
|
||||
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
|
||||
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)
|
||||
|
@@ -21,10 +21,12 @@ package org.musicpd;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@@ -178,6 +180,14 @@ public class Settings extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
/* TODO: this sure is the wrong place to request
|
||||
permissions - it will cause MPD to quit
|
||||
immediately; we should request permissions when we
|
||||
need them, but implementing that is complicated, so
|
||||
for now, we do it here to give users a quick
|
||||
solution for the problem */
|
||||
requestAllPermissions();
|
||||
|
||||
setContentView(R.layout.settings);
|
||||
mRunButton = (ToggleButton) findViewById(R.id.run);
|
||||
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
|
||||
@@ -203,6 +213,31 @@ public class Settings extends Activity {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
private void checkRequestPermission(String permission) {
|
||||
if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED)
|
||||
return;
|
||||
|
||||
try {
|
||||
this.requestPermissions(new String[]{permission}, 0);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "requestPermissions(" + permission + ") failed",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private void requestAllPermissions() {
|
||||
if (android.os.Build.VERSION.SDK_INT < 23)
|
||||
/* we don't need to request permissions on
|
||||
this old Android version */
|
||||
return;
|
||||
|
||||
/* starting with Android 6.0, we need to explicitly
|
||||
request all permissions before using them;
|
||||
mentioning them in the manifest is not enough */
|
||||
|
||||
checkRequestPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
private void connectClient() {
|
||||
mClient = new Main.Client(this, new Main.Client.Callback() {
|
||||
|
||||
|
@@ -1,5 +1,9 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
# This is a wrapper for pkg-config which helps with cross-compiling;
|
||||
# it sets up environment variables to pkg-config searches for
|
||||
# libraries in the sysroot where a copy of this script is located.
|
||||
|
||||
BIN=`dirname $0`
|
||||
ROOT=`dirname "$BIN"`
|
||||
|
||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.4'
|
||||
version = '0.21.18'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
@@ -2,12 +2,12 @@ Developer's Manual
|
||||
##################
|
||||
|
||||
Introduction
|
||||
============
|
||||
************
|
||||
|
||||
This is a guide for those who wish to hack on the MPD source code. MPD is an open project, and we are always happy about contributions. So far, more than 150 people have contributed patches. This document is work in progress. Most of it may be incomplete yet. Please help!
|
||||
|
||||
Code Style
|
||||
==========
|
||||
**********
|
||||
|
||||
* indent with tabs (width 8)
|
||||
* don't write CPP when you can write C++: use inline functions and constexpr instead of macros
|
||||
@@ -18,7 +18,6 @@ Code Style
|
||||
* classes and functions names use CamelCase; variables are lower-case with words separated by underscore
|
||||
|
||||
Some example code:
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
@@ -33,7 +32,7 @@ Some example code:
|
||||
}
|
||||
|
||||
Hacking The Source
|
||||
==================
|
||||
******************
|
||||
|
||||
MPD sources are managed in a git repository on
|
||||
`Github <https://github.com/MusicPlayerDaemon/>`_.
|
||||
@@ -59,7 +58,7 @@ possible, to be sure that you don't break any disabled code.
|
||||
Don't mix several changes in one single patch. Create a separate patch for every change. Tools like :program:`stgit` help you with that. This way, we can review your patches more easily, and we can pick the patches we like most first.
|
||||
|
||||
Basic stgit usage
|
||||
-----------------
|
||||
=================
|
||||
|
||||
stgit allows you to create a set of patches and refine all of them: you can go back to any patch at any time, and re-edit it (both the code and the commit message). You can reorder patches and insert new patches at any position. It encourages creating separate patches for tiny changes.
|
||||
|
||||
@@ -94,35 +93,7 @@ When the whole patch series is finished, convert stgit patches to git commits:
|
||||
stg commit
|
||||
|
||||
Submitting Patches
|
||||
==================
|
||||
******************
|
||||
|
||||
Send your patches to the mailing list:
|
||||
Email: `mpd-devel <mpd-devel@musicpd.org>`_
|
||||
|
||||
:program:`git pull` requests are preferred.
|
||||
|
||||
Development Tools
|
||||
=================
|
||||
|
||||
Clang Static Analyzer
|
||||
---------------------
|
||||
|
||||
The `static analyzer <http://clang-analyzer.llvm.org/>`_ is a tool that helps find bugs. To run it on the MPD code base, install LLVM and clang. configure MPD to use clang:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
./configure --enable-debug CXX=clang++ CC=clang ...
|
||||
|
||||
It is recommended to use :code:`--enable-debug`, because the analyzer
|
||||
takes advantage of :dfn:`assert()` calls, which are only enabled in
|
||||
the debug build.
|
||||
|
||||
Now run the analyzer:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
scan-build --use-c++=clang++ --use-cc=clang make
|
||||
|
||||
The options :code:`--use-c++` and :code:`--use-cc` are necessary
|
||||
because it invokes :command:`cc` for actually compiling the sources by
|
||||
default. That breaks, because MPD requires a C99 compiler.
|
||||
Submit pull requests on GitHub:
|
||||
https://github.com/MusicPlayerDaemon/MPD/pulls
|
||||
|
@@ -1,165 +0,0 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE itemizedlist PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>artist</varname>: the artist name. Its meaning is not
|
||||
well-defined; see <varname>composer</varname> and
|
||||
<varname>performer</varname> for more specific tags.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>artistsort</varname>: same as
|
||||
<varname>artist</varname>, but for sorting. This usually omits
|
||||
prefixes such as "The".
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>album</varname>: the album name.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumsort</varname>: same as <varname>album</varname>,
|
||||
but for sorting.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumartist</varname>: on multi-artist albums, this is
|
||||
the artist name which shall be used for the whole album. The
|
||||
exact meaning of this tag is not well-defined.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumartistsort</varname>: same as
|
||||
<varname>albumartist</varname>, but for sorting.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>title</varname>: the song title.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>track</varname>: the decimal track number within the
|
||||
album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>name</varname>: a name for this song. This is not the
|
||||
song title. The exact meaning of this tag is not well-defined.
|
||||
It is often used by badly configured internet radio stations
|
||||
with broken tags to squeeze both the artist name and the song
|
||||
title in one tag.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>genre</varname>: the music genre.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>date</varname>: the song's release date. This is
|
||||
usually a 4-digit year.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>composer</varname>: the artist who composed the song.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>performer</varname>: the artist who performed the song.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>comment</varname>: a human-readable comment about this
|
||||
song. The exact meaning of this tag is not well-defined.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>disc</varname>: the decimal disc number in a multi-disc
|
||||
album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_artistid</varname>: the artist id in the
|
||||
<ulink
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_albumid</varname>: the album id in the
|
||||
<ulink
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_albumartistid</varname>: the album artist
|
||||
id in the <ulink
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_trackid</varname>: the track id in the
|
||||
<ulink
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_releasetrackid</varname>: the release track
|
||||
id in the <ulink
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_workid</varname>: the work id in the
|
||||
<ulink
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
@@ -140,7 +140,6 @@ of database.
|
||||
.B auto_update_depth <N>
|
||||
Limit the depth of the directories being watched, 0 means only watch
|
||||
the music directory itself. There is no limit by default.
|
||||
.TP
|
||||
.SH REQUIRED AUDIO OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B type <type>
|
||||
@@ -164,57 +163,12 @@ Specifies how replay gain is applied. The default is "software",
|
||||
which uses an internal software volume control. "mixer" uses the
|
||||
configured (hardware) mixer control. "none" disables replay gain on
|
||||
this audio output.
|
||||
.SH OPTIONAL ALSA OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B device <dev>
|
||||
This specifies the device to use for audio output. The default is "default".
|
||||
.TP
|
||||
.B mixer_type <hardware, software or none>
|
||||
Specifies which mixer should be used for this audio output: the
|
||||
hardware mixer (available for ALSA, OSS and PulseAudio), the software
|
||||
mixer or no mixer ("none"). By default, the hardware mixer is used
|
||||
for devices which support it, and none for the others.
|
||||
.TP
|
||||
.B mixer_device <mixer dev>
|
||||
This specifies which mixer to use. The default is "default". To use
|
||||
the second sound card in a system, use "hw:1".
|
||||
.TP
|
||||
.B mixer_control <mixer ctrl>
|
||||
This specifies which mixer control to use (sometimes referred to as
|
||||
the "device"). The default is "PCM". Use "amixer scontrols" to see
|
||||
the list of possible controls.
|
||||
.TP
|
||||
.B mixer_index <mixer index>
|
||||
A number identifying the index of the named mixer control. This is
|
||||
probably only useful if your alsa device has more than one
|
||||
identically\-named mixer control. The default is "0". Use "amixer
|
||||
scontrols" to see the list of controls with their indexes.
|
||||
.TP
|
||||
.B auto_resample <yes or no>
|
||||
Setting this to "no" disables ALSA's software resampling, if the
|
||||
hardware does not support a specific sample rate. This lets MPD do
|
||||
the resampling. "yes" is the default and allows ALSA to resample.
|
||||
.TP
|
||||
.B auto_channels <yes or no>
|
||||
Setting this to "no" disables ALSA's channel conversion, if the
|
||||
hardware does not support a specific number of channels. Default: "yes".
|
||||
.TP
|
||||
.B auto_format <yes or no>
|
||||
Setting this to "no" disables ALSA's sample format conversion, if the
|
||||
hardware does not support a specific sample format. Default: "yes".
|
||||
.TP
|
||||
.B buffer_time <time in microseconds>
|
||||
This sets the length of the hardware sample buffer in microseconds. Increasing
|
||||
it may help to reduce or eliminate skipping on certain setups. Most users do
|
||||
not need to change this. The default is 500000 microseconds (0.5 seconds).
|
||||
.TP
|
||||
.B period_time <time in microseconds>
|
||||
This sets the time between hardware sample transfers in microseconds.
|
||||
Increasing this can reduce CPU usage while lowering it can reduce underrun
|
||||
errors on bandwidth-limited devices. Some users have reported good results
|
||||
with this set to 50000, but not all devices support values this high. Most
|
||||
users do not need to change this. The default is 256000000 / sample_rate(kHz),
|
||||
or 5804 microseconds for CD-quality audio.
|
||||
.SH FILES
|
||||
.TP
|
||||
.BI ~/.mpdconf
|
||||
|
263
doc/plugins.rst
263
doc/plugins.rst
@@ -4,10 +4,10 @@ Plugin reference
|
||||
.. _database_plugins:
|
||||
|
||||
Database plugins
|
||||
----------------
|
||||
================
|
||||
|
||||
simple
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
The default plugin. Stores a copy of the database in memory. A file is used for permanent storage.
|
||||
|
||||
@@ -25,7 +25,7 @@ The default plugin. Stores a copy of the database in memory. A file is used for
|
||||
- Compress the database file using gzip? Enabled by default (if built with zlib).
|
||||
|
||||
proxy
|
||||
~~~~~
|
||||
-----
|
||||
|
||||
Provides access to the database of another :program:`MPD` instance using libmpdclient. This is useful when you run mount the music directory via NFS/SMB, and the file server already runs a :program:`MPD` instance. Only the file server needs to update the database.
|
||||
|
||||
@@ -42,33 +42,33 @@ Provides access to the database of another :program:`MPD` instance using libmpdc
|
||||
* - **password**
|
||||
- The password used to log in to the "master" :program:`MPD` instance.
|
||||
* - **keepalive yes|no**
|
||||
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expensive of a very small amount of additional network traffic. Disabled by default.
|
||||
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expense of a very small amount of additional network traffic. Disabled by default.
|
||||
|
||||
upnp
|
||||
~~~~
|
||||
----
|
||||
|
||||
Provides access to UPnP media servers.
|
||||
|
||||
Storage plugins
|
||||
---------------
|
||||
===============
|
||||
|
||||
local
|
||||
~~~~~
|
||||
-----
|
||||
|
||||
The default plugin which gives :program:`MPD` access to local files. It is used when music_directory refers to a local directory.
|
||||
|
||||
curl
|
||||
~~~~
|
||||
----
|
||||
|
||||
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
|
||||
|
||||
smbclient
|
||||
~~~~~~~~~
|
||||
---------
|
||||
|
||||
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
|
||||
|
||||
nfs
|
||||
~~~
|
||||
---
|
||||
|
||||
Load music files from a NFS server. It is used when :code:`music_directory` contains a nfs:// URI according to RFC2224, for example :samp:`nfs://servername/path`.
|
||||
|
||||
@@ -81,38 +81,55 @@ This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD`
|
||||
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
|
||||
|
||||
udisks
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Mount file systems (e.g. USB sticks or other removable media) using
|
||||
the udisks2 daemon via D-Bus. To obtain a valid udisks2 URI, consult
|
||||
:ref:`the according neighbor plugin <neighbor_plugin>`.
|
||||
|
||||
It might be necessary to grant :program:`MPD` privileges to control
|
||||
:program:`udisks2` through :program:`policykit`. To do this, create a
|
||||
file called :file:`/usr/share/polkit-1/rules.d/mpd-udisks.rules` with
|
||||
the following text::
|
||||
|
||||
polkit.addRule(function(action, subject) {
|
||||
if ((action.id == "org.freedesktop.udisks2.filesystem-mount" ||
|
||||
action.id == "org.freedesktop.udisks2.filesystem-mount-other-seat") &&
|
||||
subject.user == "mpd") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
|
||||
If you run MPD as a different user, change ``mpd`` to the name of your
|
||||
MPD user.
|
||||
|
||||
.. _neighbor_plugin:
|
||||
|
||||
Neighbor plugins
|
||||
----------------
|
||||
================
|
||||
|
||||
smbclient
|
||||
~~~~~~~~~
|
||||
---------
|
||||
|
||||
Provides a list of SMB/CIFS servers on the local network.
|
||||
|
||||
udisks
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Queries the udisks2 daemon via D-Bus and obtain a list of file systems (e.g. USB sticks or other removable media).
|
||||
|
||||
upnp
|
||||
~~~~
|
||||
----
|
||||
|
||||
Provides a list of UPnP servers on the local network.
|
||||
|
||||
.. _input_plugins:
|
||||
|
||||
Input plugins
|
||||
-------------
|
||||
=============
|
||||
|
||||
alsa
|
||||
~~~~
|
||||
----
|
||||
|
||||
Allows :program:`MPD` on Linux to play audio directly from a soundcard using the scheme alsa://. Audio is formatted as 44.1 kHz 16-bit stereo (CD format). Examples:
|
||||
|
||||
@@ -125,7 +142,7 @@ Allows :program:`MPD` on Linux to play audio directly from a soundcard using the
|
||||
mpc add alsa://hw:1,0 plays audio from device hw:1,0 cdio_paranoia
|
||||
|
||||
cdio_paranoia
|
||||
~~~~~~~~~~~~~
|
||||
-------------
|
||||
|
||||
Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]". The simplest form cdda:// plays the whole disc in the default drive.
|
||||
|
||||
@@ -141,7 +158,7 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
|
||||
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
|
||||
|
||||
curl
|
||||
~~~~
|
||||
----
|
||||
|
||||
Opens remote files or streams over HTTP using libcurl.
|
||||
|
||||
@@ -163,22 +180,22 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
|
||||
- Verify the certificate's name against host? `More information <http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html>`_.
|
||||
|
||||
ffmpeg
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
|
||||
|
||||
file
|
||||
~~~~
|
||||
----
|
||||
|
||||
Opens local files
|
||||
|
||||
mms
|
||||
~~~
|
||||
---
|
||||
|
||||
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
|
||||
|
||||
nfs
|
||||
~~~
|
||||
---
|
||||
|
||||
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
|
||||
|
||||
@@ -189,7 +206,7 @@ Allows :program:`MPD` to access files on NFSv3 servers without actually mounting
|
||||
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
|
||||
|
||||
smbclient
|
||||
~~~~~~~~~
|
||||
---------
|
||||
|
||||
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
|
||||
|
||||
@@ -198,7 +215,7 @@ Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microso
|
||||
mpc add smb://servername/sharename/filename.ogg
|
||||
|
||||
qobuz
|
||||
~~~~~
|
||||
-----
|
||||
|
||||
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
|
||||
|
||||
@@ -224,10 +241,15 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
|
||||
- The `Qobuz format identifier <https://github.com/Qobuz/api-documentation/blob/master/endpoints/track/getFileUrl.md#parameters>`_, i.e. a number which chooses the format and quality to be requested from Qobuz. The default is "5" (320 kbit/s MP3).
|
||||
|
||||
tidal
|
||||
~~~~~
|
||||
-----
|
||||
|
||||
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
|
||||
|
||||
.. warning::
|
||||
|
||||
This plugin is currently defunct because Tidal has changed the
|
||||
protocol and decided not to share documentation.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
mpc add tidal://track/59727857
|
||||
@@ -250,10 +272,10 @@ Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. I
|
||||
.. _decoder_plugins:
|
||||
|
||||
Decoder plugins
|
||||
---------------
|
||||
===============
|
||||
|
||||
adplug
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Decodes AdLib files using libadplug.
|
||||
|
||||
@@ -267,17 +289,17 @@ Decodes AdLib files using libadplug.
|
||||
- The sample rate that shall be synthesized by the plugin. Defaults to 48000.
|
||||
|
||||
audiofile
|
||||
~~~~~~~~~
|
||||
---------
|
||||
|
||||
Decodes WAV and AIFF files using libaudiofile.
|
||||
|
||||
faad
|
||||
~~~~
|
||||
----
|
||||
|
||||
Decodes AAC files using libfaad.
|
||||
|
||||
ffmpeg
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Decodes various codecs using FFmpeg.
|
||||
|
||||
@@ -293,12 +315,12 @@ Decodes various codecs using FFmpeg.
|
||||
- Sets the FFmpeg muxer option probesize, which specifies probing size in bytes, i.e. the size of the data to analyze to get stream information. The `FFmpeg formats documentation <https://ffmpeg.org/ffmpeg-formats.html>`_ has more information.
|
||||
|
||||
flac
|
||||
~~~~
|
||||
----
|
||||
|
||||
Decodes FLAC files using libFLAC.
|
||||
|
||||
dsdiff
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Decodes DFF files containing DSDIFF data (e.g. SACD rips).
|
||||
|
||||
@@ -312,12 +334,12 @@ Decodes DFF files containing DSDIFF data (e.g. SACD rips).
|
||||
- Decode the least significant bit first. Default is no.
|
||||
|
||||
dsf
|
||||
~~~
|
||||
---
|
||||
|
||||
Decodes DSF files containing DSDIFF data (e.g. SACD rips).
|
||||
|
||||
fluidsynth
|
||||
~~~~~~~~~~
|
||||
----------
|
||||
|
||||
MIDI decoder based on `FluidSynth <http://www.fluidsynth.org/>`_.
|
||||
|
||||
@@ -333,7 +355,7 @@ MIDI decoder based on `FluidSynth <http://www.fluidsynth.org/>`_.
|
||||
- The absolute path of the soundfont file. Defaults to :file:`/usr/share/sounds/sf2/FluidR3_GM.sf2`.
|
||||
|
||||
gme
|
||||
~~~
|
||||
---
|
||||
|
||||
Video game music file emulator based on `game-music-emu <https://bitbucket.org/mpyne/game-music-emu/wiki/Home>`_.
|
||||
|
||||
@@ -347,7 +369,7 @@ Video game music file emulator based on `game-music-emu <https://bitbucket.org/m
|
||||
- Enable more accurate sound emulation.
|
||||
|
||||
hybrid_dsd
|
||||
~~~~~~~~~~
|
||||
----------
|
||||
|
||||
`Hybrid-DSD
|
||||
<http://dsdmaster.blogspot.de/p/bitperfect-introduces-hybrid-dsd-file.html>`_
|
||||
@@ -370,12 +392,12 @@ of the file is better.
|
||||
- This specifies whether to support gapless playback of MP3s which have the necessary headers. Useful if your MP3s have headers with incorrect information. If you have such MP3s, it is highly recommended that you fix them using `vbrfix <http://www.willwap.co.uk/Programs/vbrfix.php>`_ instead of disabling gapless MP3 playback. The default is to support gapless MP3 playback.
|
||||
|
||||
mad
|
||||
~~~
|
||||
---
|
||||
|
||||
Decodes MP3 files using `libmad <http://www.underbit.com/products/mad/>`_.
|
||||
|
||||
mikmod
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Module player based on `MikMod <http://mikmod.sourceforge.net/>`_.
|
||||
|
||||
@@ -391,7 +413,7 @@ Module player based on `MikMod <http://mikmod.sourceforge.net/>`_.
|
||||
- Sets the sample rate generated by libmikmod. Default is 44100.
|
||||
|
||||
modplug
|
||||
~~~~~~~
|
||||
-------
|
||||
|
||||
Module player based on MODPlug.
|
||||
|
||||
@@ -405,27 +427,27 @@ Module player based on MODPlug.
|
||||
- Number of times to loop the module if it uses backward loops. Default is 0 which prevents looping. -1 loops forever.
|
||||
|
||||
mpcdec
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Decodes Musepack files using `libmpcdec <http://www.musepack.net/>`_.
|
||||
|
||||
mpg123
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Decodes MP3 files using `libmpg123 <http://www.mpg123.de/>`_.
|
||||
|
||||
opus
|
||||
~~~~
|
||||
----
|
||||
|
||||
Decodes Opus files using `libopus <http://www.opus-codec.org/>`_.
|
||||
|
||||
pcm
|
||||
~~~
|
||||
---
|
||||
|
||||
Read raw PCM samples. It understands the "audio/L16" MIME type with parameters "rate" and "channels" according to RFC 2586. It also understands the MPD-specific MIME type "audio/x-mpd-float".
|
||||
|
||||
sidplay
|
||||
~~~~~~~
|
||||
-------
|
||||
|
||||
C64 SID decoder based on `libsidplayfp <https://sourceforge.net/projects/sidplay-residfp/>`_ or `libsidplay2 <https://sourceforge.net/projects/sidplay2/>`_.
|
||||
|
||||
@@ -447,23 +469,23 @@ C64 SID decoder based on `libsidplayfp <https://sourceforge.net/projects/sidplay
|
||||
- Only libsidplayfp. Absolute path to basic rom image file.
|
||||
|
||||
sndfile
|
||||
~~~~~~~
|
||||
-------
|
||||
|
||||
Decodes WAV and AIFF files using `libsndfile <http://www.mega-nerd.com/libsndfile/>`_.
|
||||
|
||||
|
||||
vorbis
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Decodes Ogg-Vorbis files using `libvorbis <http://www.xiph.org/ogg/vorbis/>`_.
|
||||
|
||||
wavpack
|
||||
~~~~~~~
|
||||
-------
|
||||
|
||||
Decodes WavPack files using `libwavpack <http://www.wavpack.com/>`_.
|
||||
|
||||
wildmidi
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
MIDI decoder based on `libwildmidi <http://www.mindwerks.net/projects/wildmidi/>`_.
|
||||
|
||||
@@ -479,10 +501,11 @@ MIDI decoder based on `libwildmidi <http://www.mindwerks.net/projects/wildmidi/>
|
||||
.. _encoder_plugins:
|
||||
|
||||
Encoder plugins
|
||||
---------------
|
||||
===============
|
||||
|
||||
flac
|
||||
~~~~
|
||||
----
|
||||
|
||||
Encodes into `FLAC <https://xiph.org/flac/>`_ (lossless).
|
||||
|
||||
.. list-table::
|
||||
@@ -495,7 +518,7 @@ Encodes into `FLAC <https://xiph.org/flac/>`_ (lossless).
|
||||
- Sets the libFLAC compression level. The levels range from 0 (fastest, least compression) to 8 (slowest, most compression).
|
||||
|
||||
lame
|
||||
~~~~
|
||||
----
|
||||
|
||||
Encodes into MP3 using the `LAME <http://lame.sourceforge.net/>`_ library.
|
||||
|
||||
@@ -511,12 +534,12 @@ Encodes into MP3 using the `LAME <http://lame.sourceforge.net/>`_ library.
|
||||
- Sets the bit rate in kilobit per second. Cannot be used with quality.
|
||||
|
||||
null
|
||||
~~~~
|
||||
----
|
||||
|
||||
Does not encode anything, passes the input PCM data as-is.
|
||||
|
||||
shine
|
||||
~~~~~
|
||||
-----
|
||||
|
||||
Encodes into MP3 using the `Shine <https://github.com/savonet/shine>`_ library.
|
||||
|
||||
@@ -530,7 +553,7 @@ Encodes into MP3 using the `Shine <https://github.com/savonet/shine>`_ library.
|
||||
- Sets the bit rate in kilobit per second.
|
||||
|
||||
twolame
|
||||
~~~~~~~
|
||||
-------
|
||||
|
||||
Encodes into MP2 using the `TwoLAME <http://www.twolame.org/>`_ library.
|
||||
|
||||
@@ -546,7 +569,7 @@ Encodes into MP2 using the `TwoLAME <http://www.twolame.org/>`_ library.
|
||||
- Sets the bit rate in kilobit per second. Cannot be used with quality.
|
||||
|
||||
opus
|
||||
~~~~
|
||||
----
|
||||
|
||||
Encodes into `Ogg Opus <http://www.opus-codec.org/>`_.
|
||||
|
||||
@@ -568,7 +591,7 @@ Encodes into `Ogg Opus <http://www.opus-codec.org/>`_.
|
||||
.. _vorbis_plugin:
|
||||
|
||||
vorbis
|
||||
~~~~~~
|
||||
------
|
||||
|
||||
Encodes into `Ogg Vorbis <http://www.vorbis.com/>`_.
|
||||
|
||||
@@ -584,13 +607,13 @@ Encodes into `Ogg Vorbis <http://www.vorbis.com/>`_.
|
||||
- Sets the bit rate in kilobit per second. Cannot be used with quality.
|
||||
|
||||
wave
|
||||
~~~~
|
||||
----
|
||||
Encodes into WAV (lossless).
|
||||
|
||||
.. _resampler_plugins:
|
||||
|
||||
Resampler plugins
|
||||
-----------------
|
||||
=================
|
||||
|
||||
The resampler can be configured in a block named resampler, for example:
|
||||
|
||||
@@ -613,12 +636,12 @@ The following table lists the resampler options valid for all plugins:
|
||||
- The name of the plugin.
|
||||
|
||||
internal
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
A resampler built into :program:`MPD`. Its quality is very poor, but its CPU usage is low. This is the fallback if :program:`MPD` was compiled without an external resampler.
|
||||
|
||||
libsamplerate
|
||||
~~~~~~~~~~~~~
|
||||
-------------
|
||||
|
||||
A resampler using `libsamplerate <http://www.mega-nerd.com/SRC/>`_ a.k.a. Secret Rabbit Code (SRC).
|
||||
|
||||
@@ -651,7 +674,7 @@ The following converter types are provided by libsamplerate:
|
||||
- Linear interpolator, very fast, poor quality.
|
||||
|
||||
soxr
|
||||
~~~~
|
||||
----
|
||||
|
||||
A resampler using `libsoxr <http://sourceforge.net/projects/soxr/>`_, the SoX Resampler library
|
||||
|
||||
@@ -674,13 +697,15 @@ Valid quality values for libsoxr:
|
||||
* "low"
|
||||
* "quick"
|
||||
|
||||
.. _output_plugins:
|
||||
|
||||
Output plugins
|
||||
--------------
|
||||
==============
|
||||
|
||||
.. _alsa_plugin:
|
||||
|
||||
alsa
|
||||
~~~~
|
||||
----
|
||||
|
||||
The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ plugin uses libasound. It is recommended if you are using Linux.
|
||||
|
||||
@@ -739,7 +764,7 @@ The following attributes can be configured at runtime using the outputset comman
|
||||
|
||||
|
||||
ao
|
||||
~~
|
||||
--
|
||||
The ao plugin uses the portable `libao <https://www.xiph.org/ao/>`_ library. Use only if there is no native plugin for your operating system.
|
||||
|
||||
.. list-table::
|
||||
@@ -756,7 +781,8 @@ The ao plugin uses the portable `libao <https://www.xiph.org/ao/>`_ library. Use
|
||||
- This specifies how many bytes to write to the audio device at once. This parameter is to work around a bug in older versions of libao on sound cards with very small buffers. The default is 1024.
|
||||
|
||||
sndio
|
||||
~~~~~
|
||||
-----
|
||||
|
||||
The sndio plugin uses the `sndio <http://www.sndio.org/>`_ library. It should normally be used on OpenBSD.
|
||||
|
||||
.. list-table::
|
||||
@@ -771,7 +797,7 @@ The sndio plugin uses the `sndio <http://www.sndio.org/>`_ library. It should no
|
||||
- Set the application buffer time in milliseconds.
|
||||
|
||||
fifo
|
||||
~~~~
|
||||
----
|
||||
|
||||
The fifo plugin writes raw PCM data to a FIFO (First In, First Out) file. The data can be read by another program.
|
||||
|
||||
@@ -784,8 +810,18 @@ The fifo plugin writes raw PCM data to a FIFO (First In, First Out) file. The da
|
||||
* - **path P**
|
||||
- This specifies the path of the FIFO to write to. Must be an absolute path. If the path does not exist, it will be created when MPD is started, and removed when MPD is stopped. The FIFO will be created with the same user and group as MPD is running as. Default permissions can be modified by using the builtin shell command umask. If a FIFO already exists at the specified path it will be reused, and will not be removed when MPD is stopped. You can use the "mkfifo" command to create this, and then you may modify the permissions to your liking.
|
||||
|
||||
haiku
|
||||
-----
|
||||
|
||||
Use the SoundPlayer API on the Haiku operating system.
|
||||
|
||||
This plugin is unmaintained and contains known bugs. It will be
|
||||
removed soon, unless there is a new maintainer.
|
||||
|
||||
|
||||
jack
|
||||
~~~~
|
||||
----
|
||||
|
||||
The jack plugin connects to a `JACK server <http://jackaudio.org/>`_.
|
||||
|
||||
.. list-table::
|
||||
@@ -808,7 +844,8 @@ The jack plugin connects to a `JACK server <http://jackaudio.org/>`_.
|
||||
- Sets the size of the ring buffer for each channel. Do not configure this value unless you know what you're doing.
|
||||
|
||||
httpd
|
||||
~~~~~
|
||||
-----
|
||||
|
||||
The httpd plugin creates a HTTP server, similar to `ShoutCast <http://www.shoutcast.com/>`_ / `IceCast <http://icecast.org/>`_. HTTP streaming clients like mplayer, VLC, and mpv can connect to it.
|
||||
|
||||
It is highly recommended to configure a fixed format, because a stream cannot switch its audio format on-the-fly when the song changes.
|
||||
@@ -822,14 +859,15 @@ It is highly recommended to configure a fixed format, because a stream cannot sw
|
||||
* - **port P**
|
||||
- Binds the HTTP server to the specified port.
|
||||
* - **bind_to_address ADDR**
|
||||
- Binds the HTTP server to the specified address (IPv4, IPv6 or UNIX socket). Multiple addresses in parallel are not supported.
|
||||
- Binds the HTTP server to the specified address (IPv4, IPv6 or local socket). Multiple addresses in parallel are not supported.
|
||||
* - **encoder NAME**
|
||||
- Chooses an encoder plugin. A list of encoder plugins can be found in the encoder plugin reference :ref:`encoder_plugins`.
|
||||
* - **max_clients MC**
|
||||
- Sets a limit, number of concurrent clients. When set to 0 no limit will apply.
|
||||
|
||||
null
|
||||
~~~~
|
||||
----
|
||||
|
||||
The null plugin does nothing. It discards everything sent to it.
|
||||
|
||||
.. list-table::
|
||||
@@ -844,7 +882,8 @@ The null plugin does nothing. It discards everything sent to it.
|
||||
.. _oss_plugin:
|
||||
|
||||
oss
|
||||
~~~
|
||||
---
|
||||
|
||||
The "Open Sound System" plugin is supported on most Unix platforms.
|
||||
|
||||
On Linux, OSS has been superseded by ALSA. Use the ALSA output plugin :ref:`alsa_plugin` instead of this one on Linux.
|
||||
@@ -872,7 +911,7 @@ The according hardware mixer plugin understands the following settings:
|
||||
- Choose a mixer control, defaulting to PCM.
|
||||
|
||||
openal
|
||||
~~~~~~
|
||||
------
|
||||
The "OpenAL" plugin uses `libopenal <http://kcat.strangesoft.net/openal.html>`_. It is supported on many platforms. Use only if there is no native plugin for your operating system.
|
||||
|
||||
.. list-table::
|
||||
@@ -885,7 +924,7 @@ The "OpenAL" plugin uses `libopenal <http://kcat.strangesoft.net/openal.html>`_.
|
||||
- Sets the device which should be used. This can be any valid OpenAL device name. If not specified, then libopenal will choose a default device.
|
||||
|
||||
osx
|
||||
~~~
|
||||
---
|
||||
The "Mac OS X" plugin uses Apple's CoreAudio API.
|
||||
|
||||
.. list-table::
|
||||
@@ -906,7 +945,7 @@ The "Mac OS X" plugin uses Apple's CoreAudio API.
|
||||
The channel map may not refer to outputs that do not exist according to the format. If the format is "*:*:1" (mono) and you have a four-channel sound card then "-1,-1,0,0" (dual mono output on the second pair of sound card outputs) is a valid channel map but "-1,-1,0,1" is not because the second channel ('1') does not exist when the output is mono.
|
||||
|
||||
pipe
|
||||
~~~~
|
||||
----
|
||||
|
||||
The pipe plugin starts a program and writes raw PCM data into its standard input.
|
||||
|
||||
@@ -922,7 +961,7 @@ The pipe plugin starts a program and writes raw PCM data into its standard input
|
||||
.. _pulse_plugin:
|
||||
|
||||
pulse
|
||||
~~~~~
|
||||
-----
|
||||
The pulse plugin connects to a `PulseAudio <http://www.freedesktop.org/wiki/Software/PulseAudio/>`_ server. Requires libpulse.
|
||||
|
||||
.. list-table::
|
||||
@@ -935,9 +974,11 @@ The pulse plugin connects to a `PulseAudio <http://www.freedesktop.org/wiki/Soft
|
||||
- Sets the host name of the PulseAudio server. By default, :program:`MPD` connects to the local PulseAudio server.
|
||||
* - **sink NAME**
|
||||
- Specifies the name of the PulseAudio sink :program:`MPD` should play on.
|
||||
* - **scale_volume FACTOR**
|
||||
- Specifies a linear scaling coefficient (ranging from 0.5 to 5.0) to apply when adjusting volume through :program:`MPD`. For example, chosing a factor equal to ``"0.7"`` means that setting the volume to 100 in :program:`MPD` will set the PulseAudio volume to 70%, and a factor equal to ``"3.5"`` means that volume 100 in :program:`MPD` corresponds to a 350% PulseAudio volume.
|
||||
|
||||
recorder
|
||||
~~~~~~~~
|
||||
--------
|
||||
The recorder plugin writes the audio played by :program:`MPD` to a file. This may be useful for recording radio streams.
|
||||
|
||||
.. list-table::
|
||||
@@ -949,13 +990,13 @@ The recorder plugin writes the audio played by :program:`MPD` to a file. This ma
|
||||
* - **path P**
|
||||
- Write to this file.
|
||||
* - **format_path P**
|
||||
- An alternative to path which provides a format string referring to tag values. The special tag iso8601 emits the current date and time in `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ format (UTC). Every time a new song starts or a new tag gets received from a radio station, a new file is opened. If the format does not render a file name, nothing is recorded. A tag name enclosed in percent signs ('%') is replaced with the tag value. Example: :file:`~/.mpd/recorder/%artist% - %title%.ogg`. Square brackets can be used to group a substring. If none of the tags referred in the group can be found, the whole group is omitted. Example: [~/.mpd/recorder/[%artist% - ]%title%.ogg] (this omits the dash when no artist tag exists; if title also doesn't exist, no file is written). The operators "|" (logical "or") and "&" (logical "and") can be used to select portions of the format string depending on the existing tag values. Example: ~/.mpd/recorder/[%title%|%name%].ogg (use the "name" tag if no title exists)
|
||||
- An alternative to path which provides a format string referring to tag values. The special tag iso8601 emits the current date and time in `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ format (UTC). Every time a new song starts or a new tag gets received from a radio station, a new file is opened. If the format does not render a file name, nothing is recorded. A tag name enclosed in percent signs ('%') is replaced with the tag value. Example: :file:`-/.mpd/recorder/%artist% - %title%.ogg`. Square brackets can be used to group a substring. If none of the tags referred in the group can be found, the whole group is omitted. Example: [-/.mpd/recorder/[%artist% - ]%title%.ogg] (this omits the dash when no artist tag exists; if title also doesn't exist, no file is written). The operators "|" (logical "or") and "&" (logical "and") can be used to select portions of the format string depending on the existing tag values. Example: -/.mpd/recorder/[%title%|%name%].ogg (use the "name" tag if no title exists)
|
||||
* - **encoder NAME**
|
||||
- Chooses an encoder plugin. A list of encoder plugins can be found in the encoder plugin reference :ref:`encoder_plugins`.
|
||||
|
||||
|
||||
shout
|
||||
~~~~~
|
||||
-----
|
||||
The shout plugin connects to a ShoutCast or IceCast server using libshout. It forwards tags to this server.
|
||||
|
||||
You must set a format.
|
||||
@@ -974,6 +1015,8 @@ You must set a format.
|
||||
- Set the timeout for the shout connection in seconds. Defaults to 2 seconds.
|
||||
* - **protocol icecast2|icecast1|shoutcast**
|
||||
- Specifies the protocol that wil be used to connect to the server. The default is "icecast2".
|
||||
* - **tls disabled|auto|auto_no_plain|rfc2818|rfc2817**
|
||||
- Specifies what kind of TLS to use. The default is "disabled" (no TLS).
|
||||
* - **mount URI**
|
||||
- Mounts the :program:`MPD` stream in the specified URI.
|
||||
* - **user USERNAME**
|
||||
@@ -997,7 +1040,7 @@ You must set a format.
|
||||
.. _sles_output:
|
||||
|
||||
sles
|
||||
~~~~
|
||||
----
|
||||
|
||||
Plugin using the `OpenSL ES <https://www.khronos.org/opensles/>`__
|
||||
audio API. Its primary use is local playback on Android, where
|
||||
@@ -1005,7 +1048,7 @@ audio API. Its primary use is local playback on Android, where
|
||||
|
||||
|
||||
solaris
|
||||
~~~~~~~
|
||||
-------
|
||||
The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
|
||||
|
||||
.. list-table::
|
||||
@@ -1017,46 +1060,80 @@ The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
|
||||
* - **device PATH**
|
||||
- Sets the path of the audio device, defaults to /dev/audio.
|
||||
|
||||
|
||||
.. _filter_plugins:
|
||||
|
||||
Filter plugins
|
||||
==============
|
||||
|
||||
normalize
|
||||
---------
|
||||
|
||||
Normalize the volume during playback (at the expense of quality).
|
||||
|
||||
|
||||
null
|
||||
----
|
||||
|
||||
A no-op filter. Audio data is returned as-is.
|
||||
|
||||
|
||||
route
|
||||
-----
|
||||
|
||||
Reroute channels.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **routes "0>0, 1>1, ..."**
|
||||
- Specifies the channel mapping.
|
||||
|
||||
|
||||
.. _playlist_plugins:
|
||||
|
||||
|
||||
Playlist plugins
|
||||
----------------
|
||||
================
|
||||
|
||||
asx
|
||||
~~~
|
||||
---
|
||||
|
||||
Reads .asx playlist files.
|
||||
|
||||
cue
|
||||
~~~
|
||||
---
|
||||
Reads .cue files.
|
||||
|
||||
embcue
|
||||
~~~~~~
|
||||
------
|
||||
Reads CUE sheets from the "CUESHEET" tag of song files.
|
||||
|
||||
m3u
|
||||
~~~
|
||||
---
|
||||
Reads .m3u playlist files.
|
||||
|
||||
extm3u
|
||||
~~~~~~
|
||||
------
|
||||
Reads extended .m3u playlist files.
|
||||
|
||||
flac
|
||||
~~~~
|
||||
----
|
||||
Reads the cuesheet metablock from a FLAC file.
|
||||
|
||||
pls
|
||||
~~~
|
||||
---
|
||||
Reads .pls playlist files.
|
||||
|
||||
rss
|
||||
~~~
|
||||
---
|
||||
Reads music links from .rss files.
|
||||
|
||||
soundcloud
|
||||
~~~~~~~~~~
|
||||
----------
|
||||
|
||||
Download playlist from SoundCloud. It accepts URIs starting with soundcloud://.
|
||||
|
||||
.. list-table::
|
||||
@@ -1069,5 +1146,5 @@ Download playlist from SoundCloud. It accepts URIs starting with soundcloud://.
|
||||
- An API key to access the SoundCloud servers.
|
||||
|
||||
xspf
|
||||
~~~~
|
||||
----
|
||||
Reads XSPF playlist files.
|
||||
|
@@ -14,6 +14,9 @@ Once the client is connected to the server, they conduct a
|
||||
conversation until the client closes the connection. The
|
||||
conversation flow is always initiated by the client.
|
||||
|
||||
All data between the client and the server is encoded in
|
||||
UTF-8.
|
||||
|
||||
The client transmits a command sequence, terminated by the
|
||||
newline character ``\n``. The server will
|
||||
respond with one or more lines, the last of which will be a
|
||||
@@ -42,9 +45,6 @@ quotation marks.
|
||||
Argument strings are separated from the command and any other
|
||||
arguments by linear white-space (' ' or '\\t').
|
||||
|
||||
All data between the client and the server is encoded in
|
||||
UTF-8.
|
||||
|
||||
Responses
|
||||
=========
|
||||
|
||||
@@ -52,6 +52,28 @@ A command returns ``OK`` on completion or
|
||||
``ACK some error`` on failure. These
|
||||
denote the end of command execution.
|
||||
|
||||
Some commands return more data before the response ends with ``OK``.
|
||||
Each line is usually in the form ``NAME: VALUE``. Example::
|
||||
|
||||
foo: bar
|
||||
OK
|
||||
|
||||
.. _binary:
|
||||
|
||||
Binary Responses
|
||||
----------------
|
||||
|
||||
Some commands can return binary data. This is initiated by a line
|
||||
containing ``binary: 1234`` (followed as usual by a newline). After
|
||||
that, the specified number of bytes of binary data follows, then a
|
||||
newline, and finally the ``OK`` line. Example::
|
||||
|
||||
foo: bar
|
||||
binary: 42
|
||||
<42 bytes>
|
||||
OK
|
||||
|
||||
|
||||
Failure responses
|
||||
-----------------
|
||||
|
||||
@@ -112,9 +134,9 @@ list begins with `command_list_begin` or
|
||||
`command_list_ok_begin` and ends with
|
||||
`command_list_end`.
|
||||
|
||||
It does not execute any commands until the list has ended.
|
||||
The return value is whatever the return for a list of commands
|
||||
is. On success for all commands,
|
||||
It does not execute any commands until the list has ended. The
|
||||
response is a concatentation of all individual responses.
|
||||
On success for all commands,
|
||||
``OK`` is returned. If a command
|
||||
fails, no more commands are executed and the appropriate
|
||||
``ACK`` error is returned. If
|
||||
@@ -144,15 +166,20 @@ syntax::
|
||||
``EXPRESSION`` is a string enclosed in parantheses which can be one
|
||||
of:
|
||||
|
||||
- ``(TAG == 'VALUE')``: match a tag value.
|
||||
``(TAG != 'VALUE')``: mismatch a tag value.
|
||||
The special tag "*any*" checks all
|
||||
tag values.
|
||||
*albumartist* looks for
|
||||
- ``(TAG == 'VALUE')``: match a tag value; if there are multiple
|
||||
values of the given type, at least one must match.
|
||||
``(TAG != 'VALUE')``: mismatch a tag value; if there are multiple
|
||||
values of the given type, none of them must match.
|
||||
The special tag ``any`` checks all
|
||||
tag types.
|
||||
``AlbumArtist`` looks for
|
||||
``VALUE`` in ``AlbumArtist``
|
||||
and falls back to ``Artist`` tags if
|
||||
``AlbumArtist`` does not exist.
|
||||
``VALUE`` is what to find.
|
||||
An empty value string means: match only if the given tag type does
|
||||
not exist at all; this implies that negation with an empty value
|
||||
checks for the existence of the given tag type.
|
||||
|
||||
- ``(TAG contains 'VALUE')`` checks if the given value is a substring
|
||||
of the tag value.
|
||||
@@ -173,12 +200,13 @@ of:
|
||||
file's time stamp with the given value (ISO 8601 or UNIX
|
||||
time stamp).
|
||||
|
||||
- ``(AudioFormat == 'SAMPLERATE:BITS:CHANNELS')``:
|
||||
compares the audio format with the given value.
|
||||
- ``(AudioFormat == 'SAMPLERATE:BITS:CHANNELS')``: compares the audio
|
||||
format with the given value. See :ref:`audio_output_format` for a
|
||||
detailed explanation.
|
||||
|
||||
- ``(AudioFormat =~ 'SAMPLERATE:BITS:CHANNELS')``:
|
||||
matches the audio format with the given mask (i.e. one
|
||||
or more attributes may be "*").
|
||||
or more attributes may be ``*``).
|
||||
|
||||
- ``(!EXPRESSION)``: negate an expression. Note that each expression
|
||||
must be enclosed in parantheses, e.g. :code:`(!(artist == 'VALUE'))`
|
||||
@@ -207,11 +235,11 @@ backslash.
|
||||
|
||||
Example expression which matches an artist named ``foo'bar"``::
|
||||
|
||||
(artist "foo\'bar\"")
|
||||
(Artist == "foo\'bar\"")
|
||||
|
||||
At the protocol level, the command must look like this::
|
||||
|
||||
find "(artist \"foo\\'bar\\\"\")"
|
||||
find "(Artist == \"foo\\'bar\\\"\")"
|
||||
|
||||
The double quotes enclosing the artist name must be escaped because
|
||||
they are inside a double-quoted ``find`` parameter. The single quote
|
||||
@@ -409,15 +437,18 @@ Querying :program:`MPD`'s status
|
||||
- ``songid``: playlist songid of the current song stopped on or playing
|
||||
- ``nextsong`` [#since_0_15]_: playlist song number of the next song to be played
|
||||
- ``nextsongid`` [#since_0_15]_: playlist songid of the next song to be played
|
||||
- ``time``: total time elapsed (of current playing/paused song)
|
||||
- ``time``: total time elapsed (of current playing/paused song) in seconds
|
||||
(deprecated, use ``elapsed`` instead)
|
||||
- ``elapsed`` [#since_0_16]_: Total time elapsed within the current song, but with higher resolution.
|
||||
- ``elapsed`` [#since_0_16]_: Total time elapsed within the
|
||||
current song in seconds, but with higher resolution.
|
||||
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
|
||||
- ``bitrate``: instantaneous bitrate in kbps
|
||||
- ``xfade``: ``crossfade`` in seconds
|
||||
- ``mixrampdb``: ``mixramp`` threshold in dB
|
||||
- ``mixrampdelay``: ``mixrampdelay`` in seconds
|
||||
- ``audio``: The format emitted by the decoder plugin during playback, format: ``*samplerate:bits:channels*``. Check the user manual for a detailed explanation.
|
||||
- ``audio``: The format emitted by the decoder plugin during
|
||||
playback, format: ``samplerate:bits:channels``. See
|
||||
:ref:`audio_output_format` for a detailed explanation.
|
||||
- ``updating_db``: ``job id``
|
||||
- ``error``: if there is an error, returns message here
|
||||
|
||||
@@ -432,7 +463,7 @@ Querying :program:`MPD`'s status
|
||||
- ``albums``: number of albums
|
||||
- ``songs``: number of songs
|
||||
- ``uptime``: daemon uptime in seconds
|
||||
- ``db_playtime``: sum of all song times in the db
|
||||
- ``db_playtime``: sum of all song times in the database in seconds
|
||||
- ``db_update``: last db update in UNIX time
|
||||
- ``playtime``: time length of music played
|
||||
|
||||
@@ -594,7 +625,7 @@ Whenever possible, ids should be used.
|
||||
Deletes the song ``SONGID`` from the
|
||||
playlist
|
||||
|
||||
:command:`move {FROM} [{START:END} | {TO}]`
|
||||
:command:`move [{FROM} | {START:END}] {TO}`
|
||||
Moves the song at ``FROM`` or range of songs
|
||||
at ``START:END`` [#since_0_15]_ to ``TO``
|
||||
in the playlist.
|
||||
@@ -714,7 +745,9 @@ and without the `.m3u` suffix).
|
||||
Some of the commands described in this section can be used to
|
||||
run playlist plugins instead of the hard-coded simple
|
||||
`m3u` parser. They can access playlists in
|
||||
the music directory (relative path including the suffix) or
|
||||
the music directory (relative path including the suffix),
|
||||
playlists in arbitrary location (absolute path including the suffix;
|
||||
allowed only for clients that are connected via local socket), or
|
||||
remote playlists (absolute URI with a supported scheme).
|
||||
|
||||
:command:`listplaylist {NAME}`
|
||||
@@ -783,7 +816,7 @@ The music database
|
||||
|
||||
Returns the file size and actual number
|
||||
of bytes read at the requested offset, followed
|
||||
by the chunk requested as raw bytes, then a
|
||||
by the chunk requested as raw bytes (see :ref:`binary`), then a
|
||||
newline and the completion code.
|
||||
|
||||
Example::
|
||||
@@ -853,8 +886,7 @@ The music database
|
||||
:command:`list {TYPE} {FILTER} [group {GROUPTYPE}]`
|
||||
Lists unique tags values of the specified type.
|
||||
``TYPE`` can be any tag supported by
|
||||
:program:`MPD` or
|
||||
*file*.
|
||||
:program:`MPD`.
|
||||
|
||||
Additional arguments may specify a :ref:`filter <filter_syntax>`.
|
||||
The *group* keyword may be used
|
||||
@@ -865,6 +897,10 @@ The music database
|
||||
|
||||
list album group albumartist
|
||||
|
||||
``list file`` was implemented in an early :program:`MPD` version,
|
||||
but does not appear to make a lot of sense. It still works (to
|
||||
avoid breaking compatibility), but is deprecated.
|
||||
|
||||
.. _command_listall:
|
||||
|
||||
:command:`listall [URI]`
|
||||
@@ -909,7 +945,7 @@ The music database
|
||||
|
||||
.. _command_lsinfo:
|
||||
|
||||
:command:`lsinfo {URI}`
|
||||
:command:`lsinfo [URI]`
|
||||
Lists the contents of the directory
|
||||
``URI``. The response contains records
|
||||
starting with ``file``,
|
||||
@@ -924,7 +960,7 @@ The music database
|
||||
This command may be used to list metadata of remote
|
||||
files (e.g. URI beginning with "http://" or "smb://").
|
||||
|
||||
Clients that are connected via UNIX domain socket may
|
||||
Clients that are connected via local socket may
|
||||
use this command to read the tags of an arbitrary local
|
||||
file (URI is an absolute path).
|
||||
|
||||
@@ -1046,7 +1082,8 @@ Stickers
|
||||
"Stickers" [#since_0_15]_ are pieces of
|
||||
information attached to existing
|
||||
:program:`MPD` objects (e.g. song files,
|
||||
directories, albums). Clients can create arbitrary name/value
|
||||
directories, albums; but currently, they are only implemented for
|
||||
song). Clients can create arbitrary name/value
|
||||
pairs. :program:`MPD` itself does not assume
|
||||
any special meaning in them.
|
||||
|
||||
@@ -1131,7 +1168,7 @@ Connection settings
|
||||
``tagtypes`` sub commands configure this
|
||||
list.
|
||||
|
||||
:command:`tagtypes disable {NAME...]`
|
||||
:command:`tagtypes disable {NAME...}`
|
||||
Remove one or more tags from the list of tag types the
|
||||
client is interested in. These will be omitted from
|
||||
responses to this client.
|
||||
@@ -1215,7 +1252,7 @@ Reflection
|
||||
:command:`config`
|
||||
Dumps configuration values that may be interesting for
|
||||
the client. This command is only permitted to "local"
|
||||
clients (connected via UNIX domain socket).
|
||||
clients (connected via local socket).
|
||||
|
||||
The following response attributes are available:
|
||||
|
||||
|
127
doc/user.rst
127
doc/user.rst
@@ -54,7 +54,7 @@ Download the source tarball from the `MPD home page <https://musicpd.org>`_ and
|
||||
In any case, you need:
|
||||
|
||||
* a C++14 compiler (e.g. gcc 6.0 or clang 3.9)
|
||||
* `Meson 0.47.2 <http://mesonbuild.com/>`__ and `Ninja
|
||||
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
<https://ninja-build.org/>`__
|
||||
* Boost 1.58
|
||||
* pkg-config
|
||||
@@ -62,16 +62,16 @@ In any case, you need:
|
||||
Each plugin usually needs a codec library, which you also need to
|
||||
install. Check the :doc:`plugins` for details about required libraries
|
||||
|
||||
For example, the following installs a fairly complete list of build dependencies on Debian Jessie:
|
||||
For example, the following installs a fairly complete list of build dependencies on Debian Buster:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
apt install g++ \
|
||||
apt install meson g++ \
|
||||
libpcre3-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
libfluidsynth-dev libgme-dev libmikmod2-dev libmodplug-dev \
|
||||
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
|
||||
libmpcdec-dev libwavpack-dev libwildmidi-dev \
|
||||
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
||||
libavcodec-dev libavformat-dev \
|
||||
@@ -91,7 +91,9 @@ For example, the following installs a fairly complete list of build dependencies
|
||||
libsystemd-dev \
|
||||
libgtest-dev \
|
||||
libboost-dev \
|
||||
libicu-dev
|
||||
libicu-dev \
|
||||
libchromaprint-dev \
|
||||
libgcrypt20-dev
|
||||
|
||||
|
||||
Now configure the source tree:
|
||||
@@ -365,10 +367,14 @@ More information can be found in the :ref:`decoder_plugins` reference.
|
||||
Configuring encoder plugins
|
||||
---------------------------
|
||||
|
||||
Encoders are used by some of the output plugins (such as shout). The encoder settings are included in the audio_output section.
|
||||
Encoders are used by some of the output plugins (such as shout). The
|
||||
encoder settings are included in the ``audio_output`` section, see :ref:`config_audio_output`.
|
||||
|
||||
More information can be found in the :ref:`encoder_plugins` reference.
|
||||
|
||||
|
||||
.. _config_audio_output:
|
||||
|
||||
Configuring audio outputs
|
||||
-------------------------
|
||||
|
||||
@@ -398,14 +404,9 @@ The following table lists the audio_output options valid for all plugins:
|
||||
- The name of the plugin
|
||||
* - **name**
|
||||
- The name of the audio output. It is visible to the client. Some plugins also use it internally, e.g. as a name registered in the PULSE server.
|
||||
* - **format**
|
||||
- Always open the audio output with the specified audio format samplerate:bits:channels), regardless of the format of the input file. This is optional for most plugins.
|
||||
|
||||
Any of the three attributes may be an asterisk to specify that this attribute should not be enforced, example: 48000:16:*. *:*:* is equal to not having a format specification.
|
||||
|
||||
The following values are valid for bits: 8 (signed 8 bit integer samples), 16, 24 (signed 24 bit integer samples padded to 32 bit), 32 (signed 32 bit integer samples), f (32 bit floating point, -1.0 to 1.0), "dsd" means DSD (Direct Stream Digital). For DSD, there are special cases such as "dsd64", which allows you to omit the sample rate (e.g. dsd512:2 for stereo DSD512, i.e. 22.5792 MHz).
|
||||
|
||||
The sample rate is special for DSD: :program:`MPD` counts the number of bytes, not bits. Thus, a DSD "bit" rate of 22.5792 MHz (DSD512) is 2822400 from :program:`MPD`'s point of view (44100*512/8).
|
||||
* - **format samplerate:bits:channels**
|
||||
- Always open the audio output with the specified audio format, regardless of the format of the input file. This is optional for most plugins.
|
||||
See :ref:`audio_output_format` for a detailed description of the value.
|
||||
* - **enabed yes|no**
|
||||
- Specifies whether this audio output is enabled when :program:`MPD` is started. By default, all audio outputs are enabled. This is just the default setting when there is no state file; with a state file, the previous state is restored.
|
||||
* - **tags yes|no**
|
||||
@@ -421,6 +422,15 @@ The following table lists the audio_output options valid for all plugins:
|
||||
implement an external mixer :ref:`external_mixer`) or no mixer
|
||||
(:samp:`none`). By default, the hardware mixer is used for
|
||||
devices which support it, and none for the others.
|
||||
* - **filters "name,...**"
|
||||
- The specified configured filters are instantiated in the given
|
||||
order. Each filter name refers to a ``filter`` block, see
|
||||
:ref:`config_filter`.
|
||||
|
||||
More information can be found in the :ref:`output_plugins` reference.
|
||||
|
||||
|
||||
.. _config_filter:
|
||||
|
||||
Configuring filters
|
||||
-------------------
|
||||
@@ -436,6 +446,9 @@ To configure a filter, add a :code:`filter` block to :file:`mpd.conf`:
|
||||
name "software volume"
|
||||
}
|
||||
|
||||
Configured filters may then be added to the ``filters`` setting of an
|
||||
``audio_output`` section, see :ref:`config_audio_output`.
|
||||
|
||||
The following table lists the filter options valid for all plugins:
|
||||
|
||||
.. list-table::
|
||||
@@ -449,6 +462,9 @@ The following table lists the filter options valid for all plugins:
|
||||
* - **name**
|
||||
- The name of the filter
|
||||
|
||||
More information can be found in the :ref:`filter_plugins` reference.
|
||||
|
||||
|
||||
Configuring playlist plugins
|
||||
----------------------------
|
||||
|
||||
@@ -485,13 +501,34 @@ reference.
|
||||
Audio Format Settings
|
||||
---------------------
|
||||
|
||||
Global Audio Format
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
.. _audio_output_format:
|
||||
|
||||
The setting audio_output_format forces :program:`MPD` to use one audio format for all outputs. Doing that is usually not a good idea. The values are the same as in format in the audio_output section.
|
||||
Global Audio Format
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The setting ``audio_output_format`` forces :program:`MPD` to use one
|
||||
audio format for all outputs. Doing that is usually not a good idea.
|
||||
|
||||
The value is specified as ``samplerate:bits:channels``.
|
||||
|
||||
Any of the three attributes may be an asterisk to specify that this
|
||||
attribute should not be enforced, example: ``48000:16:*``.
|
||||
``*:*:*`` is equal to not having a format specification.
|
||||
|
||||
The following values are valid for bits: ``8`` (signed 8 bit integer
|
||||
samples), ``16``, ``24`` (signed 24 bit integer samples padded to 32
|
||||
bit), ``32`` (signed 32 bit integer samples), ``f`` (32 bit floating
|
||||
point, -1.0 to 1.0), ``dsd`` means DSD (Direct Stream Digital). For
|
||||
DSD, there are special cases such as ``dsd64``, which allows you to
|
||||
omit the sample rate (e.g. ``dsd512:2`` for stereo DSD512,
|
||||
i.e. 22.5792 MHz).
|
||||
|
||||
The sample rate is special for DSD: :program:`MPD` counts the number
|
||||
of bytes, not bits. Thus, a DSD "bit" rate of 22.5792 MHz (DSD512) is
|
||||
2822400 from :program:`MPD`'s point of view (44100*512/8).
|
||||
|
||||
Resampler
|
||||
~~~~~~~~~
|
||||
^^^^^^^^^
|
||||
|
||||
Sometimes, music needs to be resampled before it can be played; for example, CDs use a sample rate of 44,100 Hz while many cheap audio chips can only handle 48,000 Hz. Resampling reduces the quality and consumes a lot of CPU. There are different options, some of them optimized for high quality and others for low CPU usage, but you can't have both at the same time. Often, the resampler is the component that is responsible for most of :program:`MPD`'s CPU usage. Since :program:`MPD` comes with high quality defaults, it may appear that :program:`MPD` consumes more CPU than other software.
|
||||
|
||||
@@ -504,7 +541,7 @@ Client Connections
|
||||
.. _listeners:
|
||||
|
||||
Listeners
|
||||
~~~~~~~~~
|
||||
^^^^^^^^^
|
||||
|
||||
The setting :code:`bind_to_address` specifies which addresses
|
||||
:program:`MPD` listens on for connections from clients. It can be
|
||||
@@ -531,6 +568,12 @@ choice::
|
||||
|
||||
bind_to_address "/var/run/mpd/socket"
|
||||
|
||||
On Linux, local sockets can be bound to a name without a socket inode
|
||||
on the filesystem; MPD implements this by prepending ``@`` to the
|
||||
address::
|
||||
|
||||
bind_to_address "@mpd"
|
||||
|
||||
If no port is specified, the default port is 6600. This default can
|
||||
be changed with the port setting::
|
||||
|
||||
@@ -541,7 +584,7 @@ used.
|
||||
|
||||
|
||||
Permissions and Passwords
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, all clients are unauthenticated and have a full set of permissions. This can be restricted with the settings :code:`default_permissions` and :code:`password`.
|
||||
|
||||
@@ -604,7 +647,7 @@ Other Settings
|
||||
Section :ref:`tags` contains a list of supported tags.
|
||||
|
||||
The State File
|
||||
~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The state file is a file where :program:`MPD` saves and restores its state (play queue, playback position etc.) to keep it persistent across restarts and reboots. It is an optional setting.
|
||||
|
||||
@@ -622,7 +665,7 @@ The State File
|
||||
- Auto-save the state file this number of seconds after each state change. Defaults to 120 (2 minutes).
|
||||
|
||||
The Sticker Database
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
"Stickers" are pieces of information attached to songs. Some clients
|
||||
use them to store ratings and other volatile data. This feature
|
||||
@@ -639,7 +682,7 @@ requires :program:`SQLite`, compile-time configure option
|
||||
- The location of the sticker database.
|
||||
|
||||
Resource Limitations
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
These settings are various limitations to prevent :program:`MPD` from using too many resources (denial of service).
|
||||
|
||||
@@ -661,7 +704,7 @@ These settings are various limitations to prevent :program:`MPD` from using too
|
||||
- The maximum size of the output buffer to a client (maximum response size). Default is 8192 (8 MiB).
|
||||
|
||||
Buffer Settings
|
||||
~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Do not change these unless you know what you are doing.
|
||||
|
||||
@@ -675,7 +718,7 @@ Do not change these unless you know what you are doing.
|
||||
- Adjust the size of the internal audio buffer. Default is 4096 (4 MiB).
|
||||
|
||||
Zeroconf
|
||||
~~~~~~~~
|
||||
^^^^^^^^
|
||||
|
||||
If Zeroconf support (`Avahi <http://avahi.org/>`_ or Apple's Bonjour)
|
||||
was enabled at compile time with :code:`-Dzeroconf=...`,
|
||||
@@ -761,10 +804,12 @@ You can verify whether the real-time scheduler is active with the ps command:
|
||||
|
||||
The CLS column shows the CPU scheduler; TS is the normal scheduler; FF and RR are real-time schedulers. In this example, two threads use the real-time scheduler: the output thread and the rtio (real-time I/O) thread; these two are the important ones. The database update thread uses the idle scheduler ("IDL in ps), which only gets CPU when no other process needs it.
|
||||
|
||||
Note
|
||||
~~~~
|
||||
.. note::
|
||||
|
||||
There is a rumor that real-time scheduling improves audio quality. That is not true. All it does is reduce the probability of skipping (audio buffer xruns) when the computer is under heavy load.
|
||||
There is a rumor that real-time scheduling improves audio
|
||||
quality. That is not true. All it does is reduce the probability of
|
||||
skipping (audio buffer xruns) when the computer is under heavy
|
||||
load.
|
||||
|
||||
Using MPD
|
||||
*********
|
||||
@@ -792,7 +837,7 @@ Depending on the size of your music collection and the speed of the storage, thi
|
||||
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
|
||||
|
||||
Mounting other storages into the music directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:program:`MPD` has various storage plugins of which multiple instances can be "mounted" into the music directory. This way, you can use local music, file servers and USB sticks at the same time. Example:
|
||||
|
||||
@@ -860,7 +905,7 @@ To verify if :program:`MPD` converts the audio format, enable verbose logging, a
|
||||
.. code-block:: none
|
||||
|
||||
decoder: audio_format=44100:24:2, seekable=true
|
||||
output: opened plugin=alsa name="An ALSA output"audio_format=44100:16:2
|
||||
output: opened plugin=alsa name="An ALSA output" audio_format=44100:16:2
|
||||
output: converting from 44100:24:2
|
||||
|
||||
This example shows that a 24 bit file is being played, but the sound chip cannot play 24 bit. It falls back to 16 bit, discarding 8 bit.
|
||||
@@ -887,7 +932,7 @@ Check list for bit-perfect playback:
|
||||
device (:samp:`hw:0,0` or similar).
|
||||
* Don't use software volume (setting :code:`mixer_type`).
|
||||
* Don't force :program:`MPD` to use a specific audio format (settings
|
||||
:code:`format`, :code:`audio_output_format`).
|
||||
:code:`format`, :ref:`audio_output_format <audio_output_format>`).
|
||||
* Verify that you are really doing bit-perfect playback using :program:`MPD`'s verbose log and :file:`/proc/asound/card*/pcm*p/sub*/hw_params`. Some DACs can also indicate the audio format.
|
||||
|
||||
Direct Stream Digital (DSD)
|
||||
@@ -938,18 +983,18 @@ Support
|
||||
-------
|
||||
|
||||
Getting Help
|
||||
~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The :program:`MPD` project runs a `forum <https://forum.musicpd.org/>`_ and an IRC channel (#mpd on Freenode) for requesting help. Visit the MPD help page for details on how to get help.
|
||||
|
||||
Common Problems
|
||||
~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
1. Database
|
||||
^^^^^^^^^^^
|
||||
"""""""""""
|
||||
|
||||
Question: I can't see my music in the MPD database!
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Check your :code:`music_directory` setting.
|
||||
* Does the MPD user have read permission on all music files, and read+execute permission on all music directories (and all of their parent directories)?
|
||||
@@ -957,22 +1002,22 @@ Question: I can't see my music in the MPD database!
|
||||
* Did you enable all relevant decoder plugins at compile time? :command:`mpd --version` will tell you.
|
||||
|
||||
Question: MPD doesn't read ID3 tags!
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* You probably compiled :program:`MPD` without libid3tag. :command:`mpd --version` will tell you.
|
||||
|
||||
2. Playback
|
||||
^^^^^^^^^^^
|
||||
"""""""""""
|
||||
|
||||
Question: I can't hear music on my client!
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* That problem usually follows a misunderstanding of the nature of :program:`MPD`. :program:`MPD` is a remote-controlled music player, not a music distribution system. Usually, the speakers are connected to the box where :program:`MPD` runs, and the :program:`MPD` client only sends control commands, but the client does not actually play your music.
|
||||
|
||||
:program:`MPD` has output plugins which allow hearing music on a remote host (such as httpd), but that is not :program:`MPD`'s primary design goal.
|
||||
|
||||
Question: "Device or resource busy"
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* This ALSA error means that another program uses your sound hardware exclusively. You can stop that program to allow :program:`MPD` to use it.
|
||||
|
||||
@@ -991,7 +1036,7 @@ Your bug report should contain:
|
||||
* be clear about what you expect MPD to do, and what is actually happening
|
||||
|
||||
MPD crashes
|
||||
~~~~~~~~~~~
|
||||
^^^^^^^^^^^
|
||||
|
||||
All :program:`MPD` crashes are bugs which must be fixed by a developer, and you should write a bug report. (Many crash bugs are caused by codec libraries used by :program:`MPD`, and then that library must be fixed; but in any case, the :program:`MPD` `bug tracker <https://github.com/MusicPlayerDaemon/MPD/issues>`_ is a good place to report it first if you don't know.)
|
||||
|
||||
|
41
meson.build
41
meson.build
@@ -1,8 +1,8 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.4',
|
||||
meson_version: '>= 0.47.2',
|
||||
version: '0.21.18',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
'cpp_std=c++14'
|
||||
@@ -15,12 +15,18 @@ version_cxx = vcs_tag(input: 'src/GitVersion.cxx', output: 'GitVersion.cxx')
|
||||
compiler = meson.get_compiler('cpp')
|
||||
c_compiler = meson.get_compiler('c')
|
||||
|
||||
if compiler.get_id() == 'gcc' and compiler.version().version_compare('<6')
|
||||
warning('Your GCC version is too old. You need at least version 6.')
|
||||
elif compiler.get_id() == 'clang' and compiler.version().version_compare('<3')
|
||||
warning('Your clang version is too old. You need at least version 3.')
|
||||
endif
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set_quoted('PACKAGE', meson.project_name())
|
||||
conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
conf.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
conf.set_quoted('VERSION', meson.project_version())
|
||||
conf.set_quoted('PROTOCOL_VERSION', '0.21.4')
|
||||
conf.set_quoted('PROTOCOL_VERSION', '0.21.11')
|
||||
conf.set_quoted('SYSTEM_CONFIG_FILE_LOCATION', join_paths(get_option('prefix'), get_option('sysconfdir'), 'mpd.conf'))
|
||||
|
||||
common_cppflags = [
|
||||
@@ -298,6 +304,7 @@ if enable_database
|
||||
endif
|
||||
|
||||
subdir('src/util')
|
||||
subdir('src/time')
|
||||
subdir('src/system')
|
||||
subdir('src/thread')
|
||||
subdir('src/event')
|
||||
@@ -367,8 +374,10 @@ basic_dep = declare_dependency(
|
||||
|
||||
if enable_database
|
||||
subdir('src/storage')
|
||||
subdir('src/db')
|
||||
else
|
||||
storage_glue_dep = dependency('', required: false)
|
||||
endif
|
||||
subdir('src/db')
|
||||
|
||||
if neighbor_glue_dep.found()
|
||||
sources += 'src/command/NeighborCommands.cxx'
|
||||
@@ -377,8 +386,11 @@ endif
|
||||
if archive_glue_dep.found()
|
||||
sources += [
|
||||
'src/TagArchive.cxx',
|
||||
'src/db/update/Archive.cxx',
|
||||
]
|
||||
|
||||
if enable_database
|
||||
sources += ['src/db/update/Archive.cxx']
|
||||
endif
|
||||
endif
|
||||
|
||||
if is_windows
|
||||
@@ -390,6 +402,7 @@ more_deps = []
|
||||
if is_android
|
||||
subdir('src/java')
|
||||
target_type = 'shared_library'
|
||||
target_name = 'mpd'
|
||||
link_args += [
|
||||
'-Wl,--no-undefined,-shared,-Bsymbolic',
|
||||
'-llog',
|
||||
@@ -399,12 +412,20 @@ if is_android
|
||||
declare_dependency(sources: [classes_jar]),
|
||||
java_dep,
|
||||
]
|
||||
elif is_haiku
|
||||
target_type = 'executable'
|
||||
target_name = 'mpd.nores'
|
||||
link_args += [
|
||||
'-lnetwork',
|
||||
'-lbe',
|
||||
]
|
||||
else
|
||||
target_type = 'executable'
|
||||
target_name = 'mpd'
|
||||
endif
|
||||
|
||||
mpd = build_target(
|
||||
'mpd',
|
||||
target_name,
|
||||
sources,
|
||||
target_type: target_type,
|
||||
include_directories: inc,
|
||||
@@ -443,6 +464,14 @@ endif
|
||||
|
||||
if is_haiku
|
||||
subdir('src/haiku')
|
||||
custom_target(
|
||||
'mpd',
|
||||
output: 'mpd',
|
||||
input: [mpd, rsrc],
|
||||
command: [addres, '@OUTPUT@', '@INPUT0@', '@INPUT1@'],
|
||||
install: true,
|
||||
install_dir: get_option('bindir'),
|
||||
)
|
||||
endif
|
||||
|
||||
configure_file(output: 'config.h', configuration: conf)
|
||||
|
@@ -128,6 +128,7 @@ option('mpg123', type: 'feature', description: 'MP3 decoder using libmpg123')
|
||||
option('opus', type: 'feature', description: 'Opus decoder plugin')
|
||||
option('sidplay', type: 'feature', description: 'C64 SID support via libsidplayfp or libsidplay2')
|
||||
option('sndfile', type: 'feature', description: 'libsndfile decoder plugin')
|
||||
option('tremor', type: 'feature', description: 'Fixed-point vorbis decoder plugin')
|
||||
option('vorbis', type: 'feature', description: 'Vorbis decoder plugin')
|
||||
option('wavpack', type: 'feature', description: 'WavPack decoder plugin')
|
||||
option('wildmidi', type: 'feature', description: 'WildMidi decoder plugin')
|
||||
|
@@ -15,8 +15,8 @@ libmpdclient = MesonProject(
|
||||
)
|
||||
|
||||
libogg = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
|
||||
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
|
||||
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
|
||||
'lib/libogg.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -38,8 +38,8 @@ libvorbis = AutotoolsProject(
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
|
||||
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
|
||||
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
|
||||
'lib/libopus.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.1.tar.xz',
|
||||
'a38ec4d026efb58506a99ad5cd23d5a9793b4bf415f2c4c2e9c1bb444acd1994',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.2.1.tar.xz',
|
||||
'cec7c87e9b60d174509e263ac4011b522385fd0775292e1670ecc1180c9bb6d4',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.62.0.tar.xz',
|
||||
'dab5643a5fe775ae92570b9f3df6b0ef4bc2a827a959361fb130c73b721275c1',
|
||||
'http://curl.haxx.se/download/curl-7.66.0.tar.xz',
|
||||
'dbb48088193016d079b97c5c3efde8efa56ada2ebf336e8a97d04eb8e2ed98c1',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -365,8 +365,8 @@ curl = AutotoolsProject(
|
||||
)
|
||||
|
||||
libexpat = AutotoolsProject(
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
|
||||
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_7/expat-2.2.7.tar.bz2',
|
||||
'cbc9102f4a31a8dafd42d642e9a3aa31e79a0aedaa1f6efd2795ebc83174ec18',
|
||||
'lib/libexpat.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -375,8 +375,8 @@ libexpat = AutotoolsProject(
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-3.0.0.tar.gz',
|
||||
'445d92c5fc55e4a5b115e358e60486cf8f87ee50e0103d46a02e7fb4618566a5',
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-4.0.0.tar.gz',
|
||||
'6ee77e9fe220e2d3e3b1f53cfea04fb319828cc7dbb97dd9df09e46e901d797d',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -387,12 +387,12 @@ libnfs = AutotoolsProject(
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-3.0.0',
|
||||
base='libnfs-libnfs-4.0.0',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.68.0/boost_1_68_0.tar.bz2',
|
||||
'7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7',
|
||||
'https://dl.bintray.com/boostorg/release/1.71.0/source/boost_1_71_0.tar.bz2',
|
||||
'd73a8da01e8bf8c7eda40b4c84915071a8c8a0df4a6734537ddde4a8580524ee',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@@ -108,17 +108,17 @@ static constexpr Domain cmdline_domain("cmdline");
|
||||
gcc_noreturn
|
||||
static void version(void)
|
||||
{
|
||||
printf("Music Player Daemon " VERSION " (%s)\n"
|
||||
printf("Music Player Daemon " VERSION " (%s)"
|
||||
"\n"
|
||||
"Copyright 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
||||
"Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>\n"
|
||||
"This is free software; see the source for copying conditions. There is NO\n"
|
||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
|
||||
GIT_VERSION);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
"\n"
|
||||
"Database plugins:\n",
|
||||
GIT_VERSION);
|
||||
printf("\n"
|
||||
"Database plugins:\n");
|
||||
|
||||
for (auto i = database_plugins; *i != nullptr; ++i)
|
||||
printf(" %s", (*i)->name);
|
||||
@@ -129,18 +129,18 @@ static void version(void)
|
||||
for (auto i = storage_plugins; *i != nullptr; ++i)
|
||||
printf(" %s", (*i)->name);
|
||||
|
||||
printf("\n"
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_NEIGHBOR_PLUGINS
|
||||
"\n"
|
||||
printf("\n"
|
||||
"Neighbor plugins:\n");
|
||||
for (auto i = neighbor_plugins; *i != nullptr; ++i)
|
||||
printf(" %s", (*i)->name);
|
||||
|
||||
printf("\n"
|
||||
#endif
|
||||
|
||||
printf("\n"
|
||||
"\n"
|
||||
"Decoders plugins:\n");
|
||||
|
||||
|
@@ -30,6 +30,9 @@
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
#include "db/DatabaseError.hxx"
|
||||
#include "db/Interface.hxx"
|
||||
#include "db/update/Service.hxx"
|
||||
#include "storage/StorageInterface.hxx"
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
#include "sticker/StickerDatabase.hxx"
|
||||
@@ -48,7 +51,19 @@ Instance::Instance()
|
||||
{
|
||||
}
|
||||
|
||||
Instance::~Instance() noexcept = default;
|
||||
Instance::~Instance() noexcept
|
||||
{
|
||||
#ifdef ENABLE_DATABASE
|
||||
delete update;
|
||||
|
||||
if (database != nullptr) {
|
||||
database->Close();
|
||||
database.reset();
|
||||
}
|
||||
|
||||
delete storage;
|
||||
#endif
|
||||
}
|
||||
|
||||
Partition *
|
||||
Instance::FindPartition(const char *name) noexcept
|
||||
|
@@ -41,7 +41,7 @@ class NeighborGlue;
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
#include "db/DatabaseListener.hxx"
|
||||
class Database;
|
||||
#include "db/Ptr.hxx"
|
||||
class Storage;
|
||||
class UpdateService;
|
||||
#endif
|
||||
@@ -104,7 +104,7 @@ struct Instance final
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
Database *database;
|
||||
DatabasePtr database;
|
||||
|
||||
/**
|
||||
* This is really a #CompositeStorage. To avoid heavy include
|
||||
@@ -147,7 +147,6 @@ struct Instance final
|
||||
Partition *FindPartition(const char *name) noexcept;
|
||||
|
||||
void BeginShutdownPartitions() noexcept;
|
||||
void FinishShutdownPartitions() noexcept;
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
/**
|
||||
@@ -156,7 +155,7 @@ struct Instance final
|
||||
* music_directory was configured).
|
||||
*/
|
||||
Database *GetDatabase() {
|
||||
return database;
|
||||
return database.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,8 +167,6 @@ struct Instance final
|
||||
#endif
|
||||
|
||||
void BeginShutdownUpdate() noexcept;
|
||||
void FinishShutdownUpdate() noexcept;
|
||||
void ShutdownDatabase() noexcept;
|
||||
|
||||
#ifdef ENABLE_CURL
|
||||
void LookupRemoteTag(const char *uri) noexcept;
|
||||
|
@@ -29,6 +29,8 @@
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
static LocatedUri
|
||||
LocateFileUri(const char *uri, const Client *client
|
||||
#ifdef ENABLE_DATABASE
|
||||
@@ -55,14 +57,26 @@ LocateFileUri(const char *uri, const Client *client
|
||||
}
|
||||
|
||||
static LocatedUri
|
||||
LocateAbsoluteUri(const char *uri
|
||||
LocateAbsoluteUri(UriPluginKind kind, const char *uri
|
||||
#ifdef ENABLE_DATABASE
|
||||
, const Storage *storage
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (!uri_supported_scheme(uri))
|
||||
throw std::runtime_error("Unsupported URI scheme");
|
||||
switch (kind) {
|
||||
case UriPluginKind::INPUT:
|
||||
case UriPluginKind::STORAGE: // TODO: separate check for storage plugins
|
||||
if (!uri_supported_scheme(uri))
|
||||
throw std::runtime_error("Unsupported URI scheme");
|
||||
break;
|
||||
|
||||
case UriPluginKind::PLAYLIST:
|
||||
/* for now, no validation for playlist URIs; this is
|
||||
more complicated because there are three ways to
|
||||
identify which plugin to use: URI scheme, filename
|
||||
suffix and MIME type */
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
if (storage != nullptr) {
|
||||
@@ -76,7 +90,8 @@ LocateAbsoluteUri(const char *uri
|
||||
}
|
||||
|
||||
LocatedUri
|
||||
LocateUri(const char *uri, const Client *client
|
||||
LocateUri(UriPluginKind kind,
|
||||
const char *uri, const Client *client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, const Storage *storage
|
||||
#endif
|
||||
@@ -100,7 +115,7 @@ LocateUri(const char *uri, const Client *client
|
||||
#endif
|
||||
);
|
||||
else if (uri_has_scheme(uri))
|
||||
return LocateAbsoluteUri(uri
|
||||
return LocateAbsoluteUri(kind, uri
|
||||
#ifdef ENABLE_DATABASE
|
||||
, storage
|
||||
#endif
|
||||
|
@@ -41,6 +41,12 @@ class Client;
|
||||
class Storage;
|
||||
#endif
|
||||
|
||||
enum class UriPluginKind {
|
||||
INPUT,
|
||||
STORAGE,
|
||||
PLAYLIST,
|
||||
};
|
||||
|
||||
struct LocatedUri {
|
||||
enum class Type {
|
||||
/**
|
||||
@@ -84,7 +90,8 @@ struct LocatedUri {
|
||||
* that feature is disabled if this parameter is nullptr
|
||||
*/
|
||||
LocatedUri
|
||||
LocateUri(const char *uri, const Client *client
|
||||
LocateUri(UriPluginKind kind,
|
||||
const char *uri, const Client *client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, const Storage *storage
|
||||
#endif
|
||||
|
@@ -109,7 +109,7 @@ parse_log_level(const char *value, int line)
|
||||
#endif
|
||||
|
||||
void
|
||||
log_early_init(bool verbose)
|
||||
log_early_init(bool verbose) noexcept
|
||||
{
|
||||
#ifdef ANDROID
|
||||
(void)verbose;
|
||||
@@ -171,7 +171,7 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
|
||||
#ifndef ANDROID
|
||||
|
||||
static void
|
||||
close_log_files(void)
|
||||
close_log_files() noexcept
|
||||
{
|
||||
#ifdef HAVE_SYSLOG
|
||||
LogFinishSysLog();
|
||||
@@ -181,7 +181,7 @@ close_log_files(void)
|
||||
#endif
|
||||
|
||||
void
|
||||
log_deinit(void)
|
||||
log_deinit() noexcept
|
||||
{
|
||||
#ifndef ANDROID
|
||||
close_log_files();
|
||||
@@ -213,7 +213,8 @@ void setup_log_output()
|
||||
#endif
|
||||
}
|
||||
|
||||
int cycle_log_files(void)
|
||||
int
|
||||
cycle_log_files() noexcept
|
||||
{
|
||||
#ifdef ANDROID
|
||||
return 0;
|
||||
|
@@ -31,7 +31,7 @@ struct ConfigData;
|
||||
* @param verbose true when the program is started with --verbose
|
||||
*/
|
||||
void
|
||||
log_early_init(bool verbose);
|
||||
log_early_init(bool verbose) noexcept;
|
||||
|
||||
/**
|
||||
* Throws #std::runtime_error on error.
|
||||
@@ -40,12 +40,12 @@ void
|
||||
log_init(const ConfigData &config, bool verbose, bool use_stdout);
|
||||
|
||||
void
|
||||
log_deinit();
|
||||
log_deinit() noexcept;
|
||||
|
||||
void
|
||||
setup_log_output();
|
||||
|
||||
int
|
||||
cycle_log_files();
|
||||
cycle_log_files() noexcept;
|
||||
|
||||
#endif /* LOG_H */
|
||||
|
89
src/Main.cxx
89
src/Main.cxx
@@ -185,19 +185,16 @@ InitStorage(const ConfigData &config, EventLoop &event_loop)
|
||||
static bool
|
||||
glue_db_init_and_load(const ConfigData &config)
|
||||
{
|
||||
instance->database =
|
||||
CreateConfiguredDatabase(config, instance->event_loop,
|
||||
instance->io_thread.GetEventLoop(),
|
||||
*instance);
|
||||
if (instance->database == nullptr)
|
||||
auto db = CreateConfiguredDatabase(config, instance->event_loop,
|
||||
instance->io_thread.GetEventLoop(),
|
||||
*instance);
|
||||
if (!db)
|
||||
return true;
|
||||
|
||||
if (instance->database->GetPlugin().RequireStorage()) {
|
||||
if (db->GetPlugin().RequireStorage()) {
|
||||
InitStorage(config, instance->io_thread.GetEventLoop());
|
||||
|
||||
if (instance->storage == nullptr) {
|
||||
delete instance->database;
|
||||
instance->database = nullptr;
|
||||
LogDefault(config_domain,
|
||||
"Found database setting without "
|
||||
"music_directory - disabling database");
|
||||
@@ -211,22 +208,24 @@ glue_db_init_and_load(const ConfigData &config)
|
||||
}
|
||||
|
||||
try {
|
||||
instance->database->Open();
|
||||
db->Open();
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("Failed to open database plugin"));
|
||||
}
|
||||
|
||||
auto *db = dynamic_cast<SimpleDatabase *>(instance->database);
|
||||
if (db == nullptr)
|
||||
instance->database = std::move(db);
|
||||
|
||||
auto *sdb = dynamic_cast<SimpleDatabase *>(instance->database.get());
|
||||
if (sdb == nullptr)
|
||||
return true;
|
||||
|
||||
instance->update = new UpdateService(config,
|
||||
instance->event_loop, *db,
|
||||
instance->event_loop, *sdb,
|
||||
static_cast<CompositeStorage &>(*instance->storage),
|
||||
*instance);
|
||||
|
||||
/* run database update after daemonization? */
|
||||
return db->FileExists();
|
||||
return sdb->FileExists();
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -351,27 +350,6 @@ Instance::BeginShutdownUpdate() noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
Instance::FinishShutdownUpdate() noexcept
|
||||
{
|
||||
#ifdef ENABLE_DATABASE
|
||||
delete update;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
Instance::ShutdownDatabase() noexcept
|
||||
{
|
||||
#ifdef ENABLE_DATABASE
|
||||
if (instance->database != nullptr) {
|
||||
instance->database->Close();
|
||||
delete instance->database;
|
||||
}
|
||||
|
||||
delete instance->storage;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
Instance::BeginShutdownPartitions() noexcept
|
||||
{
|
||||
@@ -381,12 +359,6 @@ Instance::BeginShutdownPartitions() noexcept
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
Instance::FinishShutdownPartitions() noexcept
|
||||
{
|
||||
partitions.clear();
|
||||
}
|
||||
|
||||
void
|
||||
Instance::OnIdle(unsigned flags)
|
||||
{
|
||||
@@ -523,18 +495,19 @@ static int
|
||||
mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
|
||||
{
|
||||
ConfigureFS(raw_config);
|
||||
AtScopeExit() { DeinitFS(); };
|
||||
|
||||
glue_mapper_init(raw_config);
|
||||
|
||||
initPermissions(raw_config);
|
||||
spl_global_init(raw_config);
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
archive_plugin_init_all();
|
||||
const ScopeArchivePluginsInit archive_plugins_init;
|
||||
#endif
|
||||
|
||||
pcm_convert_global_init(raw_config);
|
||||
|
||||
decoder_plugin_init_all(raw_config);
|
||||
const ScopeDecoderPluginsInit decoder_plugins_init(raw_config);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
const bool create_db = InitDatabaseAndStorage(raw_config);
|
||||
@@ -553,9 +526,9 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
|
||||
}
|
||||
|
||||
client_manager_init(raw_config);
|
||||
input_stream_global_init(raw_config,
|
||||
instance->io_thread.GetEventLoop());
|
||||
playlist_list_global_init(raw_config);
|
||||
const ScopeInputPluginsInit input_plugins_init(raw_config,
|
||||
instance->io_thread.GetEventLoop());
|
||||
const ScopePlaylistPluginsInit playlist_plugins_init(raw_config);
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
daemonize_commit();
|
||||
@@ -564,7 +537,7 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
|
||||
#ifndef ANDROID
|
||||
setup_log_output();
|
||||
|
||||
SignalHandlersInit(instance->event_loop);
|
||||
const ScopeSignalHandlersInit signal_handlers_init(instance->event_loop);
|
||||
#endif
|
||||
|
||||
instance->io_thread.Start();
|
||||
@@ -652,34 +625,10 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
|
||||
}
|
||||
#endif
|
||||
|
||||
instance->FinishShutdownUpdate();
|
||||
instance->ShutdownDatabase();
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
sticker_global_finish();
|
||||
#endif
|
||||
|
||||
playlist_list_global_finish();
|
||||
input_stream_global_finish();
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
mapper_finish();
|
||||
#endif
|
||||
|
||||
DeinitFS();
|
||||
|
||||
instance->FinishShutdownPartitions();
|
||||
command_finish();
|
||||
decoder_plugin_deinit_all();
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
archive_plugin_deinit_all();
|
||||
#endif
|
||||
instance->rtio_thread.Stop();
|
||||
instance->io_thread.Stop();
|
||||
#ifndef ANDROID
|
||||
SignalHandlersFinish();
|
||||
#endif
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
@@ -58,11 +58,6 @@ mapper_init(AllocatedPath &&_playlist_dir)
|
||||
mapper_set_playlist_dir(std::move(_playlist_dir));
|
||||
}
|
||||
|
||||
void
|
||||
mapper_finish() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
|
||||
AllocatedPath
|
||||
|
@@ -37,9 +37,6 @@ class AllocatedPath;
|
||||
void
|
||||
mapper_init(AllocatedPath &&playlist_dir);
|
||||
|
||||
void
|
||||
mapper_finish() noexcept;
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
|
||||
/**
|
||||
|
@@ -43,7 +43,15 @@ struct MusicChunk;
|
||||
/**
|
||||
* Meta information for #MusicChunk.
|
||||
*/
|
||||
struct MusicChunkInfo {
|
||||
struct alignas(8) MusicChunkInfo {
|
||||
/* align to multiple of 8 bytes, which adds padding at the
|
||||
end, so the size of MusicChunk::data is also a multiple of
|
||||
8 bytes; this is a workaround for a bug in the DSD_U32 and
|
||||
DoP converters which require processing 8 bytes at a time,
|
||||
discarding the remainder */
|
||||
/* TODO: once all converters have been fixed, we should remove
|
||||
this workaround */
|
||||
|
||||
/** the next chunk in a linked list */
|
||||
MusicChunkPtr next;
|
||||
|
||||
@@ -119,6 +127,10 @@ struct MusicChunk : MusicChunkInfo {
|
||||
/** the data (probably PCM) */
|
||||
uint8_t data[CHUNK_SIZE - sizeof(MusicChunkInfo)];
|
||||
|
||||
/* TODO: remove this check once all converters have been fixed
|
||||
(see comment in struct MusicChunkInfo for details) */
|
||||
static_assert(sizeof(data) % 8 == 0, "Wrong alignment");
|
||||
|
||||
/**
|
||||
* Prepares appending to the music chunk. Returns a buffer
|
||||
* where you may write into. After you are finished, call
|
||||
|
@@ -21,8 +21,8 @@
|
||||
#include "db/PlaylistVector.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
@@ -134,7 +134,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info,
|
||||
const auto *const name_fs_end =
|
||||
FindStringSuffix(name_fs_str,
|
||||
PATH_LITERAL(PLAYLIST_FILE_SUFFIX));
|
||||
if (name_fs_end == nullptr)
|
||||
if (name_fs_end == nullptr ||
|
||||
/* no empty playlist names (raw file name = ".m3u") */
|
||||
name_fs_end == name_fs_str)
|
||||
return false;
|
||||
|
||||
FileInfo fi;
|
||||
|
@@ -38,6 +38,10 @@ struct ReplayGainTuple {
|
||||
return gain > -100;
|
||||
}
|
||||
|
||||
static constexpr ReplayGainTuple Undefined() noexcept {
|
||||
return {-200.0f, 0.0f};
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
float CalculateScale(const ReplayGainConfig &config) const noexcept;
|
||||
};
|
||||
@@ -49,6 +53,13 @@ struct ReplayGainInfo {
|
||||
return track.IsDefined() || album.IsDefined();
|
||||
}
|
||||
|
||||
static constexpr ReplayGainInfo Undefined() noexcept {
|
||||
return {
|
||||
ReplayGainTuple::Undefined(),
|
||||
ReplayGainTuple::Undefined(),
|
||||
};
|
||||
}
|
||||
|
||||
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
|
||||
return mode == ReplayGainMode::ALBUM
|
||||
? (album.IsDefined() ? album : track)
|
||||
|
@@ -94,7 +94,8 @@ SongLoader::LoadSong(const char *uri_utf8) const
|
||||
assert(uri_utf8 != nullptr);
|
||||
#endif
|
||||
|
||||
const auto located_uri = LocateUri(uri_utf8, client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT,
|
||||
uri_utf8, client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, storage
|
||||
#endif
|
||||
|
@@ -27,7 +27,7 @@
|
||||
#include "TagPrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
|
||||
#define SONG_FILE "file: "
|
||||
|
@@ -27,7 +27,7 @@
|
||||
#include "tag/ParseName.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "tag/Builder.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
|
@@ -98,8 +98,6 @@ Song::UpdateFile(Storage &storage) noexcept
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
|
||||
Song *
|
||||
@@ -145,6 +143,8 @@ Song::UpdateFileInArchive(ArchiveFile &archive) noexcept
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* ENABLE_DATABASE */
|
||||
|
||||
bool
|
||||
DetachedSong::LoadFile(Path path) noexcept
|
||||
{
|
||||
|
@@ -119,7 +119,7 @@ try {
|
||||
TextFile file(config.path);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
const SongLoader song_loader(partition.instance.database,
|
||||
const SongLoader song_loader(partition.instance.GetDatabase(),
|
||||
partition.instance.storage);
|
||||
#else
|
||||
const SongLoader song_loader(nullptr, nullptr);
|
||||
|
@@ -28,7 +28,7 @@
|
||||
#include "db/Stats.hxx"
|
||||
#include "system/Clock.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
@@ -124,7 +124,7 @@ stats_print(Response &r, const Partition &partition)
|
||||
std::lround(partition.pc.GetTotalPlayTime().count()));
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
const Database *db = partition.instance.database;
|
||||
const Database *db = partition.instance.GetDatabase();
|
||||
if (db != nullptr)
|
||||
db_stats_print(r, *db);
|
||||
#endif
|
||||
|
@@ -25,8 +25,9 @@
|
||||
void
|
||||
tag_print_types(Response &r) noexcept
|
||||
{
|
||||
const auto tag_mask = global_tag_mask & r.GetTagMask();
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
|
||||
if (IsTagEnabled(i))
|
||||
if (tag_mask.Test(TagType(i)))
|
||||
r.Format("tagtype: %s\n", tag_item_names[i]);
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "TimePrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "util/TimeISO8601.hxx"
|
||||
#include "time/ISO8601.hxx"
|
||||
|
||||
void
|
||||
time_print(Response &r, const char *name,
|
||||
|
@@ -49,7 +49,7 @@ static bool archive_plugins_enabled[ARRAY_SIZE(archive_plugins) - 1];
|
||||
if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins])
|
||||
|
||||
const ArchivePlugin *
|
||||
archive_plugin_from_suffix(const char *suffix)
|
||||
archive_plugin_from_suffix(const char *suffix) noexcept
|
||||
{
|
||||
if (suffix == nullptr)
|
||||
return nullptr;
|
||||
@@ -63,7 +63,7 @@ archive_plugin_from_suffix(const char *suffix)
|
||||
}
|
||||
|
||||
const ArchivePlugin *
|
||||
archive_plugin_from_name(const char *name)
|
||||
archive_plugin_from_name(const char *name) noexcept
|
||||
{
|
||||
archive_plugins_for_each_enabled(plugin)
|
||||
if (strcmp(plugin->name, name) == 0)
|
||||
@@ -81,7 +81,8 @@ void archive_plugin_init_all(void)
|
||||
}
|
||||
}
|
||||
|
||||
void archive_plugin_deinit_all(void)
|
||||
void
|
||||
archive_plugin_deinit_all() noexcept
|
||||
{
|
||||
archive_plugins_for_each_enabled(plugin)
|
||||
if (plugin->finish != nullptr)
|
||||
|
@@ -33,10 +33,10 @@ extern const ArchivePlugin *const archive_plugins[];
|
||||
/* interface for using plugins */
|
||||
|
||||
const ArchivePlugin *
|
||||
archive_plugin_from_suffix(const char *suffix);
|
||||
archive_plugin_from_suffix(const char *suffix) noexcept;
|
||||
|
||||
const ArchivePlugin *
|
||||
archive_plugin_from_name(const char *name);
|
||||
archive_plugin_from_name(const char *name) noexcept;
|
||||
|
||||
/* this is where we "load" all the "plugins" ;-) */
|
||||
void
|
||||
@@ -44,6 +44,17 @@ archive_plugin_init_all();
|
||||
|
||||
/* this is where we "unload" all the "plugins" */
|
||||
void
|
||||
archive_plugin_deinit_all();
|
||||
archive_plugin_deinit_all() noexcept;
|
||||
|
||||
class ScopeArchivePluginsInit {
|
||||
public:
|
||||
ScopeArchivePluginsInit() {
|
||||
archive_plugin_init_all();
|
||||
}
|
||||
|
||||
~ScopeArchivePluginsInit() noexcept {
|
||||
archive_plugin_deinit_all();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "../ArchiveVisitor.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <zzip/zzip.h>
|
||||
@@ -120,9 +121,19 @@ ZzipArchiveFile::OpenStream(const char *pathname,
|
||||
Mutex &mutex)
|
||||
{
|
||||
ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0);
|
||||
if (_file == nullptr)
|
||||
throw FormatRuntimeError("not found in the ZIP file: %s",
|
||||
pathname);
|
||||
if (_file == nullptr) {
|
||||
const auto error = (zzip_error_t)zzip_error(dir->dir);
|
||||
switch (error) {
|
||||
case ZZIP_ENOENT:
|
||||
throw FormatFileNotFound("Failed to open '%s' in ZIP file",
|
||||
pathname);
|
||||
|
||||
default:
|
||||
throw FormatRuntimeError("Failed to open '%s' in ZIP file: %s",
|
||||
pathname,
|
||||
zzip_strerror(error));
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<ZzipInputStream>(dir, pathname,
|
||||
mutex,
|
||||
|
@@ -35,6 +35,6 @@ extern size_t client_max_command_list_size;
|
||||
extern size_t client_max_output_buffer_size;
|
||||
|
||||
CommandResult
|
||||
client_process_line(Client &client, char *line);
|
||||
client_process_line(Client &client, char *line) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -30,7 +30,7 @@
|
||||
|
||||
static CommandResult
|
||||
client_process_command_list(Client &client, bool list_ok,
|
||||
std::list<std::string> &&list)
|
||||
std::list<std::string> &&list) noexcept
|
||||
{
|
||||
CommandResult ret = CommandResult::OK;
|
||||
unsigned num = 0;
|
||||
@@ -51,7 +51,7 @@ client_process_command_list(Client &client, bool list_ok,
|
||||
}
|
||||
|
||||
CommandResult
|
||||
client_process_line(Client &client, char *line)
|
||||
client_process_line(Client &client, char *line) noexcept
|
||||
{
|
||||
CommandResult ret;
|
||||
|
||||
|
@@ -206,9 +206,10 @@ static constexpr struct command commands[] = {
|
||||
|
||||
static constexpr unsigned num_commands = ARRAY_SIZE(commands);
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
command_available(gcc_unused const Partition &partition,
|
||||
gcc_unused const struct command *cmd)
|
||||
gcc_unused const struct command *cmd) noexcept
|
||||
{
|
||||
#ifdef ENABLE_SQLITE
|
||||
if (StringIsEqual(cmd->cmd, "sticker"))
|
||||
@@ -235,7 +236,7 @@ command_available(gcc_unused const Partition &partition,
|
||||
|
||||
static CommandResult
|
||||
PrintAvailableCommands(Response &r, const Partition &partition,
|
||||
unsigned permission)
|
||||
unsigned permission) noexcept
|
||||
{
|
||||
for (unsigned i = 0; i < num_commands; ++i) {
|
||||
const struct command *cmd = &commands[i];
|
||||
@@ -249,7 +250,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
|
||||
}
|
||||
|
||||
static CommandResult
|
||||
PrintUnavailableCommands(Response &r, unsigned permission)
|
||||
PrintUnavailableCommands(Response &r, unsigned permission) noexcept
|
||||
{
|
||||
for (unsigned i = 0; i < num_commands; ++i) {
|
||||
const struct command *cmd = &commands[i];
|
||||
@@ -276,7 +277,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
|
||||
}
|
||||
|
||||
void
|
||||
command_init()
|
||||
command_init() noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
/* ensure that the command list is sorted */
|
||||
@@ -285,13 +286,9 @@ command_init()
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
command_finish()
|
||||
{
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const struct command *
|
||||
command_lookup(const char *name)
|
||||
command_lookup(const char *name) noexcept
|
||||
{
|
||||
unsigned a = 0, b = num_commands, i;
|
||||
|
||||
@@ -313,7 +310,7 @@ command_lookup(const char *name)
|
||||
|
||||
static bool
|
||||
command_check_request(const struct command *cmd, Response &r,
|
||||
unsigned permission, Request args)
|
||||
unsigned permission, Request args) noexcept
|
||||
{
|
||||
if (cmd->permission != (permission & cmd->permission)) {
|
||||
r.FormatError(ACK_ERROR_PERMISSION,
|
||||
@@ -347,7 +344,7 @@ command_check_request(const struct command *cmd, Response &r,
|
||||
|
||||
static const struct command *
|
||||
command_checked_lookup(Response &r, unsigned permission,
|
||||
const char *cmd_name, Request args)
|
||||
const char *cmd_name, Request args) noexcept
|
||||
{
|
||||
const struct command *cmd = command_lookup(cmd_name);
|
||||
if (cmd == nullptr) {
|
||||
@@ -365,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
|
||||
}
|
||||
|
||||
CommandResult
|
||||
command_process(Client &client, unsigned num, char *line)
|
||||
try {
|
||||
command_process(Client &client, unsigned num, char *line) noexcept
|
||||
{
|
||||
Response r(client, num);
|
||||
|
||||
/* get the command name (first word on the line) */
|
||||
@@ -394,34 +391,33 @@ try {
|
||||
char *argv[COMMAND_ARGV_MAX];
|
||||
Request args(argv, 0);
|
||||
|
||||
/* now parse the arguments (quoted or unquoted) */
|
||||
try {
|
||||
/* now parse the arguments (quoted or unquoted) */
|
||||
|
||||
while (true) {
|
||||
if (args.size == COMMAND_ARGV_MAX) {
|
||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||
return CommandResult::ERROR;
|
||||
while (true) {
|
||||
if (args.size == COMMAND_ARGV_MAX) {
|
||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
char *a = tokenizer.NextParam();
|
||||
if (a == nullptr)
|
||||
break;
|
||||
|
||||
argv[args.size++] = a;
|
||||
}
|
||||
|
||||
char *a = tokenizer.NextParam();
|
||||
if (a == nullptr)
|
||||
break;
|
||||
/* look up and invoke the command handler */
|
||||
|
||||
argv[args.size++] = a;
|
||||
const struct command *cmd =
|
||||
command_checked_lookup(r, client.GetPermission(),
|
||||
cmd_name, args);
|
||||
if (cmd == nullptr)
|
||||
return CommandResult::ERROR;
|
||||
|
||||
return cmd->handler(client, args, r);
|
||||
} catch (...) {
|
||||
PrintError(r, std::current_exception());
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
/* look up and invoke the command handler */
|
||||
|
||||
const struct command *cmd =
|
||||
command_checked_lookup(r, client.GetPermission(),
|
||||
cmd_name, args);
|
||||
|
||||
CommandResult ret = cmd
|
||||
? cmd->handler(client, args, r)
|
||||
: CommandResult::ERROR;
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
Response r(client, num);
|
||||
PrintError(r, std::current_exception());
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
@@ -25,12 +25,9 @@
|
||||
class Client;
|
||||
|
||||
void
|
||||
command_init();
|
||||
|
||||
void
|
||||
command_finish();
|
||||
command_init() noexcept;
|
||||
|
||||
CommandResult
|
||||
command_process(Client &client, unsigned num, char *line);
|
||||
command_process(Client &client, unsigned num, char *line) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -266,9 +266,12 @@ handle_list(Client &client, Request args, Response &r)
|
||||
}
|
||||
|
||||
std::unique_ptr<SongFilter> filter;
|
||||
TagType group = TAG_NUM_OF_ITEM_TYPES;
|
||||
std::vector<TagType> tag_types;
|
||||
|
||||
if (args.size == 1) {
|
||||
if (args.size == 1 &&
|
||||
/* parantheses are the syntax for filter expressions: no
|
||||
compatibility mode */
|
||||
args.front()[0] != '(') {
|
||||
/* for compatibility with < 0.12.0 */
|
||||
if (tagType != TAG_ALBUM) {
|
||||
r.FormatError(ACK_ERROR_ARG,
|
||||
@@ -281,20 +284,31 @@ handle_list(Client &client, Request args, Response &r)
|
||||
args.shift()));
|
||||
}
|
||||
|
||||
if (args.size >= 2 &&
|
||||
StringIsEqual(args[args.size - 2], "group")) {
|
||||
while (args.size >= 2 &&
|
||||
StringIsEqual(args[args.size - 2], "group")) {
|
||||
const char *s = args[args.size - 1];
|
||||
group = tag_name_parse_i(s);
|
||||
const auto 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;
|
||||
}
|
||||
|
||||
if (group == tagType ||
|
||||
std::find(tag_types.begin(), tag_types.end(),
|
||||
group) != tag_types.end()) {
|
||||
r.Error(ACK_ERROR_ARG, "Conflicting group");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
tag_types.emplace_back(group);
|
||||
|
||||
args.pop_back();
|
||||
args.pop_back();
|
||||
}
|
||||
|
||||
tag_types.emplace_back(tagType);
|
||||
|
||||
if (!args.empty()) {
|
||||
filter.reset(new SongFilter());
|
||||
try {
|
||||
@@ -307,13 +321,9 @@ handle_list(Client &client, Request args, Response &r)
|
||||
filter->Optimize();
|
||||
}
|
||||
|
||||
if (tagType < TAG_NUM_OF_ITEM_TYPES && tagType == group) {
|
||||
r.Error(ACK_ERROR_ARG, "Conflicting group");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
PrintUniqueTags(r, client.GetPartition(),
|
||||
tagType, group, filter.get());
|
||||
{&tag_types.front(), tag_types.size()},
|
||||
filter.get());
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
|
@@ -37,9 +37,11 @@
|
||||
#include "fs/FileInfo.hxx"
|
||||
#include "fs/DirectoryReader.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "input/Error.hxx"
|
||||
#include "LocateUri.hxx"
|
||||
#include "TimePrint.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h> /* for PRIu64 */
|
||||
@@ -216,7 +218,7 @@ handle_read_comments(Client &client, Request args, Response &r)
|
||||
|
||||
const char *const uri = args.front();
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
@@ -256,7 +258,11 @@ find_stream_art(const char *directory, Mutex &mutex)
|
||||
|
||||
try {
|
||||
return InputStream::OpenReady(art_file.c_str(), mutex);
|
||||
} catch (const std::exception &e) {}
|
||||
} catch (...) {
|
||||
auto e = std::current_exception();
|
||||
if (!IsFileNotFound(e))
|
||||
LogError(e);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -285,8 +291,11 @@ read_stream_art(Response &r, const char *uri, size_t offset)
|
||||
uint8_t buffer[CHUNK_SIZE];
|
||||
size_t read_size;
|
||||
|
||||
is->Seek(offset);
|
||||
read_size = is->Read(&buffer, CHUNK_SIZE);
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
is->Seek(offset);
|
||||
read_size = is->Read(&buffer, CHUNK_SIZE);
|
||||
}
|
||||
|
||||
r.Format("size: %" PRIoffset "\n"
|
||||
"binary: %u\n",
|
||||
@@ -322,7 +331,7 @@ handle_album_art(Client &client, Request args, Response &r)
|
||||
const char *uri = args.front();
|
||||
size_t offset = args.ParseUnsigned(1);
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
|
@@ -35,7 +35,7 @@
|
||||
#include "decoder/DecoderPrint.hxx"
|
||||
#include "ls.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
@@ -99,7 +99,7 @@ handle_listfiles(Client &client, Request args, Response &r)
|
||||
/* default is root directory */
|
||||
const auto uri = args.GetOptional(0, "");
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::STORAGE, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
@@ -219,7 +219,7 @@ handle_lsinfo(Client &client, Request args, Response &r)
|
||||
compatibility, work around this here */
|
||||
uri = "";
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
@@ -294,7 +294,7 @@ handle_update(Client &client, Request args, Response &r, bool discard)
|
||||
if (update != nullptr)
|
||||
return handle_update(r, *update, path, discard);
|
||||
|
||||
Database *db = client.GetInstance().database;
|
||||
Database *db = client.GetInstance().GetDatabase();
|
||||
if (db != nullptr)
|
||||
return handle_update(r, *db, path, discard);
|
||||
#else
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "PlaylistCommands.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "db/DatabasePlaylist.hxx"
|
||||
#include "CommandError.hxx"
|
||||
#include "PlaylistSave.hxx"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "PlaylistError.hxx"
|
||||
#include "db/PlaylistVector.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "BulkEdit.hxx"
|
||||
#include "playlist/PlaylistQueue.hxx"
|
||||
#include "playlist/Print.hxx"
|
||||
@@ -35,9 +37,10 @@
|
||||
#include "client/Response.hxx"
|
||||
#include "Mapper.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "LocateUri.hxx"
|
||||
|
||||
bool
|
||||
playlist_commands_available() noexcept
|
||||
@@ -66,22 +69,43 @@ handle_save(Client &client, Request args, gcc_unused Response &r)
|
||||
CommandResult
|
||||
handle_load(Client &client, Request args, gcc_unused Response &r)
|
||||
{
|
||||
const auto uri = LocateUri(UriPluginKind::PLAYLIST, args.front(),
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
RangeArg range = args.ParseOptional(1, RangeArg::All());
|
||||
|
||||
const ScopeBulkEdit bulk_edit(client.GetPartition());
|
||||
|
||||
auto &playlist = client.GetPlaylist();
|
||||
const unsigned old_size = playlist.GetLength();
|
||||
|
||||
const SongLoader loader(client);
|
||||
playlist_open_into_queue(args.front(),
|
||||
playlist_open_into_queue(uri,
|
||||
range.start, range.end,
|
||||
client.GetPlaylist(),
|
||||
playlist,
|
||||
client.GetPlayerControl(), loader);
|
||||
|
||||
/* invoke the RemoteTagScanner on all newly added songs */
|
||||
auto &instance = client.GetInstance();
|
||||
const unsigned new_size = playlist.GetLength();
|
||||
for (unsigned i = old_size; i < new_size; ++i)
|
||||
instance.LookupRemoteTag(playlist.queue.Get(i).GetURI());
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
CommandResult
|
||||
handle_listplaylist(Client &client, Request args, Response &r)
|
||||
{
|
||||
const char *const name = args.front();
|
||||
const auto name = LocateUri(UriPluginKind::PLAYLIST, args.front(),
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
|
||||
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
|
||||
name, false))
|
||||
@@ -93,7 +117,12 @@ handle_listplaylist(Client &client, Request args, Response &r)
|
||||
CommandResult
|
||||
handle_listplaylistinfo(Client &client, Request args, Response &r)
|
||||
{
|
||||
const char *const name = args.front();
|
||||
const auto name = LocateUri(UriPluginKind::PLAYLIST, args.front(),
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
|
||||
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
|
||||
name, true))
|
||||
|
@@ -83,7 +83,8 @@ handle_add(Client &client, Request args, Response &r)
|
||||
here */
|
||||
uri = "";
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
|
@@ -23,8 +23,8 @@
|
||||
#include "StorageCommands.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "CommandError.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "client/Client.hxx"
|
||||
@@ -209,7 +209,7 @@ handle_mount(Client &client, Request args, Response &r)
|
||||
instance.EmitIdle(IDLE_MOUNT);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.database)) {
|
||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
||||
try {
|
||||
db->Mount(local_uri, remote_uri);
|
||||
} catch (...) {
|
||||
@@ -253,7 +253,7 @@ handle_unmount(Client &client, Request args, Response &r)
|
||||
destroy here */
|
||||
instance.update->CancelMount(local_uri);
|
||||
|
||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.database)) {
|
||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
||||
if (db->Unmount(local_uri))
|
||||
// TODO: call Instance::OnDatabaseModified()?
|
||||
instance.EmitIdle(IDLE_DATABASE);
|
||||
|
@@ -29,6 +29,8 @@ ServerSocketAddGeneric(ServerSocket &server_socket, const char *address, unsigne
|
||||
server_socket.AddPort(port);
|
||||
} else if (address[0] == '/' || address[0] == '~') {
|
||||
server_socket.AddPath(ParsePath(address));
|
||||
} else if (address[0] == '@') {
|
||||
server_socket.AddAbstract(address);
|
||||
} else {
|
||||
server_socket.AddHost(address, port);
|
||||
}
|
||||
|
@@ -23,14 +23,14 @@
|
||||
class ServerSocket;
|
||||
|
||||
/**
|
||||
* Sets the address or unix socket of a ServerSocket instance
|
||||
* Sets the address or local socket of a ServerSocket instance
|
||||
* There are three possible ways
|
||||
* 1) Set address to a valid ip address and specify port.
|
||||
* server_socket will listen on this address/port tuple.
|
||||
* 2) Set address to null and specify port.
|
||||
* server_socket will listen on ANY address on that port.
|
||||
* 3) Set address to a path of a unix socket. port is ignored.
|
||||
* server_socket will listen on this unix socket.
|
||||
* 3) Set address to a path of a local socket. port is ignored.
|
||||
* server_socket will listen on this local socket.
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
*
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "Configured.hxx"
|
||||
#include "DatabaseGlue.hxx"
|
||||
#include "Interface.hxx"
|
||||
#include "config/Data.hxx"
|
||||
#include "config/Param.hxx"
|
||||
#include "config/Block.hxx"
|
||||
@@ -26,7 +27,7 @@
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
Database *
|
||||
DatabasePtr
|
||||
CreateConfiguredDatabase(const ConfigData &config,
|
||||
EventLoop &main_event_loop, EventLoop &io_event_loop,
|
||||
DatabaseListener &listener)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -20,10 +20,11 @@
|
||||
#ifndef MPD_DB_CONFIG_HXX
|
||||
#define MPD_DB_CONFIG_HXX
|
||||
|
||||
#include "Ptr.hxx"
|
||||
|
||||
struct ConfigData;
|
||||
class EventLoop;
|
||||
class DatabaseListener;
|
||||
class Database;
|
||||
|
||||
/**
|
||||
* Read database configuration settings and create a #Database
|
||||
@@ -32,7 +33,7 @@ class Database;
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
*/
|
||||
Database *
|
||||
DatabasePtr
|
||||
CreateConfiguredDatabase(const ConfigData &config,
|
||||
EventLoop &main_event_loop, EventLoop &io_event_loop,
|
||||
DatabaseListener &listener);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -18,13 +18,14 @@
|
||||
*/
|
||||
|
||||
#include "DatabaseGlue.hxx"
|
||||
#include "Interface.hxx"
|
||||
#include "Registry.hxx"
|
||||
#include "DatabaseError.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "DatabasePlugin.hxx"
|
||||
|
||||
Database *
|
||||
DatabasePtr
|
||||
DatabaseGlobalInit(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -20,12 +20,11 @@
|
||||
#ifndef MPD_DATABASE_GLUE_HXX
|
||||
#define MPD_DATABASE_GLUE_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
#include "Ptr.hxx"
|
||||
|
||||
struct ConfigBlock;
|
||||
class EventLoop;
|
||||
class DatabaseListener;
|
||||
class Database;
|
||||
|
||||
/**
|
||||
* Initialize the database library.
|
||||
@@ -34,7 +33,7 @@ class Database;
|
||||
*
|
||||
* @param block the database configuration block
|
||||
*/
|
||||
Database *
|
||||
DatabasePtr
|
||||
DatabaseGlobalInit(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -26,10 +26,11 @@
|
||||
#ifndef MPD_DATABASE_PLUGIN_HXX
|
||||
#define MPD_DATABASE_PLUGIN_HXX
|
||||
|
||||
#include "Ptr.hxx"
|
||||
|
||||
struct ConfigBlock;
|
||||
class EventLoop;
|
||||
class DatabaseListener;
|
||||
class Database;
|
||||
|
||||
struct DatabasePlugin {
|
||||
/**
|
||||
@@ -52,10 +53,10 @@ struct DatabasePlugin {
|
||||
* @param io_event_loop the #EventLoop running on the
|
||||
* #EventThread, i.e. the one used for background I/O
|
||||
*/
|
||||
Database *(*create)(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
DatabasePtr (*create)(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
constexpr bool RequireStorage() const {
|
||||
return flags & FLAG_REQUIRE_STORAGE;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -34,7 +34,8 @@
|
||||
#include "PlaylistInfo.hxx"
|
||||
#include "Interface.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -186,42 +187,29 @@ PrintSongUris(Response &r, Partition &partition,
|
||||
}
|
||||
|
||||
static void
|
||||
PrintUniqueTags(Response &r, TagType tag_type,
|
||||
const std::set<std::string> &values)
|
||||
PrintUniqueTags(Response &r, ConstBuffer<TagType> tag_types,
|
||||
const RecursiveMap<std::string> &map) noexcept
|
||||
{
|
||||
const char *const name = tag_item_names[tag_type];
|
||||
for (const auto &i : values)
|
||||
r.Format("%s: %s\n", name, i.c_str());
|
||||
}
|
||||
const char *const name = tag_item_names[tag_types.front()];
|
||||
tag_types.pop_front();
|
||||
|
||||
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;
|
||||
}
|
||||
for (const auto &i : map) {
|
||||
r.Format("%s: %s\n", name, i.first.c_str());
|
||||
|
||||
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);
|
||||
if (!tag_types.empty())
|
||||
PrintUniqueTags(r, tag_types, i.second);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PrintUniqueTags(Response &r, Partition &partition,
|
||||
TagType type, TagType group,
|
||||
ConstBuffer<TagType> tag_types,
|
||||
const SongFilter *filter)
|
||||
{
|
||||
assert(type < TAG_NUM_OF_ITEM_TYPES);
|
||||
|
||||
const Database &db = partition.GetDatabaseOrThrow();
|
||||
|
||||
const DatabaseSelection selection("", true, filter);
|
||||
|
||||
PrintGroupedUniqueTags(r, type, group,
|
||||
db.CollectUniqueTags(selection, type, group));
|
||||
PrintUniqueTags(r, tag_types,
|
||||
db.CollectUniqueTags(selection, tag_types));
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
template<typename T> struct ConstBuffer;
|
||||
enum TagType : uint8_t;
|
||||
class TagMask;
|
||||
class SongFilter;
|
||||
@@ -45,7 +46,7 @@ PrintSongUris(Response &r, Partition &partition,
|
||||
|
||||
void
|
||||
PrintUniqueTags(Response &r, Partition &partition,
|
||||
TagType type, TagType group,
|
||||
ConstBuffer<TagType> tag_types,
|
||||
const SongFilter *filter);
|
||||
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -25,15 +25,14 @@
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
struct DatabasePlugin;
|
||||
struct DatabaseStats;
|
||||
struct DatabaseSelection;
|
||||
struct LightSong;
|
||||
class TagMask;
|
||||
template<typename Key> class RecursiveMap;
|
||||
template<typename T> struct ConstBuffer;
|
||||
|
||||
class Database {
|
||||
const DatabasePlugin &plugin;
|
||||
@@ -106,13 +105,14 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect unique values of the given tag type.
|
||||
* Collect unique values of the given tag types. Each item in
|
||||
* the #tag_types parameter results in one nesting level in
|
||||
* the return value.
|
||||
*
|
||||
* Throws on error.
|
||||
*/
|
||||
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;
|
||||
virtual RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
ConstBuffer<TagType> tag_types) const = 0;
|
||||
|
||||
/**
|
||||
* Throws on error.
|
||||
|
29
src/db/Ptr.hxx
Normal file
29
src/db/Ptr.hxx
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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_DATABASE_PTR_HXX
|
||||
#define MPD_DATABASE_PTR_HXX
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Database;
|
||||
|
||||
typedef std::unique_ptr<Database> DatabasePtr;
|
||||
|
||||
#endif
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -21,36 +21,32 @@
|
||||
#include "Interface.hxx"
|
||||
#include "song/LightSong.hxx"
|
||||
#include "tag/VisitFallback.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
|
||||
static void
|
||||
CollectTags(std::set<std::string> &result,
|
||||
const Tag &tag,
|
||||
TagType tag_type) noexcept
|
||||
CollectUniqueTags(RecursiveMap<std::string> &result,
|
||||
const Tag &tag,
|
||||
ConstBuffer<TagType> tag_types) noexcept
|
||||
{
|
||||
VisitTagWithFallbackOrEmpty(tag, tag_type, [&result](const char *value){
|
||||
result.emplace(value);
|
||||
if (tag_types.empty())
|
||||
return;
|
||||
|
||||
const auto tag_type = tag_types.shift();
|
||||
|
||||
VisitTagWithFallbackOrEmpty(tag, tag_type, [&result, &tag, tag_types](const char *value){
|
||||
CollectUniqueTags(result[value], tag, tag_types);
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
CollectGroupTags(std::map<std::string, std::set<std::string>> &result,
|
||||
const Tag &tag,
|
||||
TagType tag_type,
|
||||
TagType group) noexcept
|
||||
{
|
||||
VisitTagWithFallbackOrEmpty(tag, group, [&](const char *group_name){
|
||||
CollectTags(result[group_name], tag, tag_type);
|
||||
});
|
||||
}
|
||||
|
||||
std::map<std::string, std::set<std::string>>
|
||||
RecursiveMap<std::string>
|
||||
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group)
|
||||
ConstBuffer<TagType> tag_types)
|
||||
{
|
||||
std::map<std::string, std::set<std::string>> result;
|
||||
RecursiveMap<std::string> result;
|
||||
|
||||
db.Visit(selection, [&result, tag_type, group](const LightSong &song){
|
||||
CollectGroupTags(result, song.tag, tag_type, group);
|
||||
db.Visit(selection, [&result, tag_types](const LightSong &song){
|
||||
CollectUniqueTags(result, song.tag, tag_types);
|
||||
});
|
||||
|
||||
return result;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -29,9 +29,11 @@
|
||||
class TagMask;
|
||||
class Database;
|
||||
struct DatabaseSelection;
|
||||
template<typename Key> class RecursiveMap;
|
||||
template<typename T> struct ConstBuffer;
|
||||
|
||||
std::map<std::string, std::set<std::string>>
|
||||
RecursiveMap<std::string>
|
||||
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group);
|
||||
ConstBuffer<TagType> tag_types);
|
||||
|
||||
#endif
|
||||
|
@@ -18,11 +18,11 @@
|
||||
*/
|
||||
|
||||
#include "VHelper.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "song/LightSong.hxx"
|
||||
#include "song/Filter.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
DatabaseVisitorHelper::DatabaseVisitorHelper(const DatabaseSelection &_selection,
|
||||
|
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "Visitor.hxx"
|
||||
#include "Selection.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
@@ -9,6 +9,11 @@ db_api_dep = declare_dependency(
|
||||
link_with: db_api,
|
||||
)
|
||||
|
||||
if not enable_database
|
||||
db_glue_dep = db_api_dep
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
subdir('plugins')
|
||||
|
||||
db_glue_sources = [
|
||||
|
@@ -38,6 +38,8 @@
|
||||
#include "tag/Tag.hxx"
|
||||
#include "tag/Mask.hxx"
|
||||
#include "tag/ParseName.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
@@ -112,10 +114,10 @@ public:
|
||||
ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
static Database *Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
static DatabasePtr Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
void Open() override;
|
||||
void Close() noexcept override;
|
||||
@@ -127,9 +129,8 @@ public:
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const override;
|
||||
|
||||
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
TagType group) const override;
|
||||
RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
ConstBuffer<TagType> tag_types) const override;
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
@@ -412,8 +413,7 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
|
||||
static bool
|
||||
SendGroup(mpd_connection *connection, TagType group)
|
||||
{
|
||||
if (group == TAG_NUM_OF_ITEM_TYPES)
|
||||
return true;
|
||||
assert(group != TAG_NUM_OF_ITEM_TYPES);
|
||||
|
||||
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
|
||||
const auto tag = Convert(group);
|
||||
@@ -428,6 +428,19 @@ SendGroup(mpd_connection *connection, TagType group)
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
SendGroup(mpd_connection *connection, ConstBuffer<TagType> group)
|
||||
{
|
||||
while (!group.empty()) {
|
||||
if (!SendGroup(connection, group.back()))
|
||||
return false;
|
||||
|
||||
group.pop_back();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
||||
const ConfigBlock &block)
|
||||
:Database(proxy_db_plugin),
|
||||
@@ -440,12 +453,12 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
||||
{
|
||||
}
|
||||
|
||||
Database *
|
||||
DatabasePtr
|
||||
ProxyDatabase::Create(EventLoop &loop, EventLoop &,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block)
|
||||
{
|
||||
return new ProxyDatabase(loop, listener, block);
|
||||
return std::make_unique<ProxyDatabase>(loop, listener, block);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -568,7 +581,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
|
||||
if (!is_idle) {
|
||||
// TODO: can this happen?
|
||||
IdleMonitor::Schedule();
|
||||
return false;
|
||||
SocketMonitor::Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned idle = (unsigned)mpd_recv_idle(connection, false);
|
||||
@@ -586,7 +600,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
|
||||
idle_received |= idle;
|
||||
is_idle = false;
|
||||
IdleMonitor::Schedule();
|
||||
return false;
|
||||
SocketMonitor::Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -981,17 +996,20 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
|
||||
helper.Commit();
|
||||
}
|
||||
|
||||
std::map<std::string, std::set<std::string>>
|
||||
RecursiveMap<std::string>
|
||||
ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group) const
|
||||
ConstBuffer<TagType> tag_types) const
|
||||
try {
|
||||
// TODO: eliminate the const_cast
|
||||
const_cast<ProxyDatabase *>(this)->EnsureConnected();
|
||||
|
||||
enum mpd_tag_type tag_type2 = Convert(tag_type);
|
||||
enum mpd_tag_type tag_type2 = Convert(tag_types.back());
|
||||
if (tag_type2 == MPD_TAG_COUNT)
|
||||
throw std::runtime_error("Unsupported tag");
|
||||
|
||||
auto group = tag_types;
|
||||
group.pop_back();
|
||||
|
||||
if (!mpd_search_db_tags(connection, tag_type2) ||
|
||||
!SendConstraints(connection, selection) ||
|
||||
!SendGroup(connection, group))
|
||||
@@ -1000,44 +1018,33 @@ try {
|
||||
if (!mpd_search_commit(connection))
|
||||
ThrowError(connection);
|
||||
|
||||
std::map<std::string, std::set<std::string>> result;
|
||||
RecursiveMap<std::string> result;
|
||||
std::vector<RecursiveMap<std::string> *> position;
|
||||
position.emplace_back(&result);
|
||||
|
||||
if (group == TAG_NUM_OF_ITEM_TYPES) {
|
||||
auto &values = result[std::string()];
|
||||
while (auto *pair = mpd_recv_pair(connection)) {
|
||||
AtScopeExit(this, pair) {
|
||||
mpd_return_pair(connection, pair);
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
const auto current_type = tag_name_parse_i(pair->name);
|
||||
if (current_type == TAG_NUM_OF_ITEM_TYPES)
|
||||
continue;
|
||||
auto it = std::find(tag_types.begin(), tag_types.end(),
|
||||
current_type);
|
||||
if (it == tag_types.end())
|
||||
continue;
|
||||
|
||||
if (current_type == tag_type)
|
||||
values.emplace(pair->value);
|
||||
}
|
||||
} else {
|
||||
std::set<std::string> *current_group = nullptr;
|
||||
size_t i = std::distance(tag_types.begin(), it);
|
||||
if (i > position.size())
|
||||
continue;
|
||||
|
||||
while (auto *pair = mpd_recv_pair(connection)) {
|
||||
AtScopeExit(this, pair) {
|
||||
mpd_return_pair(connection, pair);
|
||||
};
|
||||
if (i + 1 < position.size())
|
||||
position.resize(i + 1);
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
auto &parent = *position[i];
|
||||
position.emplace_back(&parent[pair->value]);
|
||||
}
|
||||
|
||||
if (!mpd_response_finish(connection))
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -37,22 +37,25 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Directory::Directory(std::string &&_path_utf8, Directory *_parent)
|
||||
Directory::Directory(std::string &&_path_utf8, Directory *_parent) noexcept
|
||||
:parent(_parent),
|
||||
path(std::move(_path_utf8))
|
||||
{
|
||||
}
|
||||
|
||||
Directory::~Directory()
|
||||
Directory::~Directory() noexcept
|
||||
{
|
||||
delete mounted_database;
|
||||
if (mounted_database != nullptr) {
|
||||
mounted_database->Close();
|
||||
mounted_database.reset();
|
||||
}
|
||||
|
||||
songs.clear_and_dispose(Song::Disposer());
|
||||
children.clear_and_dispose(DeleteDisposer());
|
||||
}
|
||||
|
||||
void
|
||||
Directory::Delete()
|
||||
Directory::Delete() noexcept
|
||||
{
|
||||
assert(holding_db_lock());
|
||||
assert(parent != nullptr);
|
||||
@@ -70,7 +73,7 @@ Directory::GetName() const noexcept
|
||||
}
|
||||
|
||||
Directory *
|
||||
Directory::CreateChild(const char *name_utf8)
|
||||
Directory::CreateChild(const char *name_utf8) noexcept
|
||||
{
|
||||
assert(holding_db_lock());
|
||||
assert(name_utf8 != nullptr);
|
||||
@@ -160,7 +163,7 @@ Directory::LookupDirectory(const char *uri) noexcept
|
||||
}
|
||||
|
||||
void
|
||||
Directory::AddSong(Song *song)
|
||||
Directory::AddSong(Song *song) noexcept
|
||||
{
|
||||
assert(holding_db_lock());
|
||||
assert(song != nullptr);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "util/Compiler.h"
|
||||
#include "db/Visitor.hxx"
|
||||
#include "db/PlaylistVector.hxx"
|
||||
#include "db/Ptr.hxx"
|
||||
#include "Song.hxx"
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
@@ -43,7 +44,6 @@ static constexpr unsigned DEVICE_INARCHIVE = -1;
|
||||
static constexpr unsigned DEVICE_CONTAINER = -2;
|
||||
|
||||
class SongFilter;
|
||||
class Database;
|
||||
|
||||
struct Directory {
|
||||
static constexpr auto link_mode = boost::intrusive::normal_link;
|
||||
@@ -96,21 +96,21 @@ struct Directory {
|
||||
* If this is not nullptr, then this directory does not really
|
||||
* exist, but is a mount point for another #Database.
|
||||
*/
|
||||
Database *mounted_database = nullptr;
|
||||
DatabasePtr mounted_database;
|
||||
|
||||
public:
|
||||
Directory(std::string &&_path_utf8, Directory *_parent);
|
||||
~Directory();
|
||||
Directory(std::string &&_path_utf8, Directory *_parent) noexcept;
|
||||
~Directory() noexcept;
|
||||
|
||||
/**
|
||||
* Create a new root #Directory object.
|
||||
*/
|
||||
gcc_malloc gcc_returns_nonnull
|
||||
static Directory *NewRoot() {
|
||||
static Directory *NewRoot() noexcept {
|
||||
return new Directory(std::string(), nullptr);
|
||||
}
|
||||
|
||||
bool IsMount() const {
|
||||
bool IsMount() const noexcept {
|
||||
return mounted_database != nullptr;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ public:
|
||||
*
|
||||
* Caller must lock the #db_mutex.
|
||||
*/
|
||||
void Delete();
|
||||
void Delete() noexcept;
|
||||
|
||||
/**
|
||||
* Create a new #Directory object as a child of the given one.
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
*
|
||||
* @param name_utf8 the UTF-8 encoded name of the new sub directory
|
||||
*/
|
||||
Directory *CreateChild(const char *name_utf8);
|
||||
Directory *CreateChild(const char *name_utf8) noexcept;
|
||||
|
||||
/**
|
||||
* Caller must lock the #db_mutex.
|
||||
@@ -149,7 +149,7 @@ public:
|
||||
*
|
||||
* Caller must lock the #db_mutex.
|
||||
*/
|
||||
Directory *MakeChild(const char *name_utf8) {
|
||||
Directory *MakeChild(const char *name_utf8) noexcept {
|
||||
Directory *child = FindChild(name_utf8);
|
||||
if (child == nullptr)
|
||||
child = CreateChild(name_utf8);
|
||||
@@ -242,7 +242,7 @@ public:
|
||||
* Add a song object to this directory. Its "parent" attribute must
|
||||
* be set already.
|
||||
*/
|
||||
void AddSong(Song *song);
|
||||
void AddSong(Song *song) noexcept;
|
||||
|
||||
/**
|
||||
* Remove a song object from this directory (which effectively
|
||||
|
@@ -25,7 +25,7 @@
|
||||
#include "PlaylistDatabase.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -42,6 +42,8 @@
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#ifdef ENABLE_ZLIB
|
||||
@@ -60,8 +62,7 @@ inline SimpleDatabase::SimpleDatabase(const ConfigBlock &block)
|
||||
#ifdef ENABLE_ZLIB
|
||||
compress(block.GetBlockValue("compress", true)),
|
||||
#endif
|
||||
cache_path(block.GetPath("cache_directory")),
|
||||
prefixed_light_song(nullptr)
|
||||
cache_path(block.GetPath("cache_directory"))
|
||||
{
|
||||
if (path.IsNull())
|
||||
throw std::runtime_error("No \"path\" parameter specified");
|
||||
@@ -84,12 +85,12 @@ inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path,
|
||||
prefixed_light_song(nullptr) {
|
||||
}
|
||||
|
||||
Database *
|
||||
DatabasePtr
|
||||
SimpleDatabase::Create(EventLoop &, EventLoop &,
|
||||
gcc_unused DatabaseListener &listener,
|
||||
const ConfigBlock &block)
|
||||
{
|
||||
return new SimpleDatabase(block);
|
||||
return std::make_unique<SimpleDatabase>(block);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -219,6 +220,7 @@ SimpleDatabase::GetSong(const char *uri) const
|
||||
|
||||
prefixed_light_song =
|
||||
new PrefixedLightSong(*song, r.directory->GetPath());
|
||||
r.directory->mounted_database->ReturnSong(song);
|
||||
return prefixed_light_song;
|
||||
}
|
||||
|
||||
@@ -251,7 +253,7 @@ void
|
||||
SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const noexcept
|
||||
{
|
||||
assert(song != nullptr);
|
||||
assert(song == &light_song.Get() || song == prefixed_light_song);
|
||||
assert(song == prefixed_light_song || song == &light_song.Get());
|
||||
|
||||
if (prefixed_light_song != nullptr) {
|
||||
delete prefixed_light_song;
|
||||
@@ -329,11 +331,11 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
|
||||
"No such directory");
|
||||
}
|
||||
|
||||
std::map<std::string, std::set<std::string>>
|
||||
RecursiveMap<std::string>
|
||||
SimpleDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type, TagType group) const
|
||||
ConstBuffer<TagType> tag_types) const
|
||||
{
|
||||
return ::CollectUniqueTags(*this, selection, tag_type, group);
|
||||
return ::CollectUniqueTags(*this, selection, tag_types);
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
@@ -390,13 +392,13 @@ SimpleDatabase::Save()
|
||||
}
|
||||
|
||||
void
|
||||
SimpleDatabase::Mount(const char *uri, Database *db)
|
||||
SimpleDatabase::Mount(const char *uri, DatabasePtr db)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(uri != nullptr);
|
||||
assert(db != nullptr);
|
||||
#endif
|
||||
assert(db != nullptr);
|
||||
assert(*uri != 0);
|
||||
|
||||
ScopeDatabaseLock protect;
|
||||
@@ -411,7 +413,7 @@ SimpleDatabase::Mount(const char *uri, Database *db)
|
||||
"Parent not found");
|
||||
|
||||
Directory *mnt = r.directory->CreateChild(r.uri);
|
||||
mnt->mounted_database = db;
|
||||
mnt->mounted_database = std::move(db);
|
||||
}
|
||||
|
||||
static constexpr bool
|
||||
@@ -441,27 +443,21 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||
#ifndef ENABLE_ZLIB
|
||||
constexpr bool compress = false;
|
||||
#endif
|
||||
auto db = new SimpleDatabase(cache_path / name_fs,
|
||||
compress);
|
||||
try {
|
||||
db->Open();
|
||||
} catch (...) {
|
||||
delete db;
|
||||
throw;
|
||||
}
|
||||
auto db = std::make_unique<SimpleDatabase>(cache_path / name_fs,
|
||||
compress);
|
||||
db->Open();
|
||||
|
||||
// TODO: update the new database instance?
|
||||
|
||||
try {
|
||||
Mount(local_uri, db);
|
||||
Mount(local_uri, std::move(db));
|
||||
} catch (...) {
|
||||
db->Close();
|
||||
delete db;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
inline Database *
|
||||
inline DatabasePtr
|
||||
SimpleDatabase::LockUmountSteal(const char *uri) noexcept
|
||||
{
|
||||
ScopeDatabaseLock protect;
|
||||
@@ -470,8 +466,7 @@ SimpleDatabase::LockUmountSteal(const char *uri) noexcept
|
||||
if (r.uri != nullptr || !r.directory->IsMount())
|
||||
return nullptr;
|
||||
|
||||
Database *db = r.directory->mounted_database;
|
||||
r.directory->mounted_database = nullptr;
|
||||
auto db = std::move(r.directory->mounted_database);
|
||||
r.directory->Delete();
|
||||
|
||||
return db;
|
||||
@@ -480,12 +475,11 @@ SimpleDatabase::LockUmountSteal(const char *uri) noexcept
|
||||
bool
|
||||
SimpleDatabase::Unmount(const char *uri) noexcept
|
||||
{
|
||||
Database *db = LockUmountSteal(uri);
|
||||
auto db = LockUmountSteal(uri);
|
||||
if (db == nullptr)
|
||||
return false;
|
||||
|
||||
db->Close();
|
||||
delete db;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -21,6 +21,7 @@
|
||||
#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
|
||||
|
||||
#include "db/Interface.hxx"
|
||||
#include "db/Ptr.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "song/LightSong.hxx"
|
||||
#include "util/Manual.hxx"
|
||||
@@ -57,7 +58,7 @@ class SimpleDatabase : public Database {
|
||||
* A buffer for GetSong() when prefixing the #LightSong
|
||||
* instance from a mounted #Database.
|
||||
*/
|
||||
mutable PrefixedLightSong *prefixed_light_song;
|
||||
mutable PrefixedLightSong *prefixed_light_song = nullptr;
|
||||
|
||||
/**
|
||||
* A buffer for GetSong().
|
||||
@@ -68,15 +69,14 @@ class SimpleDatabase : public Database {
|
||||
mutable unsigned borrowed_song_count;
|
||||
#endif
|
||||
|
||||
public:
|
||||
SimpleDatabase(const ConfigBlock &block);
|
||||
|
||||
SimpleDatabase(AllocatedPath &&_path, bool _compress) noexcept;
|
||||
|
||||
public:
|
||||
static Database *Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
static DatabasePtr Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
gcc_pure
|
||||
Directory &GetRoot() noexcept {
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
* success, this object gains ownership of the given #Database
|
||||
*/
|
||||
gcc_nonnull_all
|
||||
void Mount(const char *uri, Database *db);
|
||||
void Mount(const char *uri, DatabasePtr db);
|
||||
|
||||
/**
|
||||
* Throws #std::runtime_error on error.
|
||||
@@ -122,9 +122,8 @@ public:
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const override;
|
||||
|
||||
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
TagType group) const override;
|
||||
RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
ConstBuffer<TagType> tag_types) const override;
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
@@ -142,7 +141,7 @@ private:
|
||||
*/
|
||||
void Load();
|
||||
|
||||
Database *LockUmountSteal(const char *uri) noexcept;
|
||||
DatabasePtr LockUmountSteal(const char *uri) noexcept;
|
||||
};
|
||||
|
||||
extern const DatabasePlugin simple_db_plugin;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -40,10 +40,11 @@
|
||||
#include "tag/Mask.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
#include "util/SplitString.hxx"
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -82,10 +83,10 @@ public:
|
||||
:Database(upnp_db_plugin),
|
||||
event_loop(_event_loop) {}
|
||||
|
||||
static Database *Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block) noexcept;
|
||||
static DatabasePtr Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
DatabaseListener &listener,
|
||||
const ConfigBlock &block) noexcept;
|
||||
|
||||
void Open() override;
|
||||
void Close() noexcept override;
|
||||
@@ -97,9 +98,8 @@ public:
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const override;
|
||||
|
||||
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
TagType group) const override;
|
||||
RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
|
||||
ConstBuffer<TagType> tag_types) const override;
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
@@ -146,12 +146,12 @@ private:
|
||||
const UPnPDirObject& dirent) const;
|
||||
};
|
||||
|
||||
Database *
|
||||
DatabasePtr
|
||||
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
|
||||
gcc_unused DatabaseListener &listener,
|
||||
const ConfigBlock &) noexcept
|
||||
{
|
||||
return new UpnpDatabase(io_event_loop);
|
||||
return std::make_unique<UpnpDatabase>(io_event_loop);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -624,11 +624,11 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
|
||||
helper.Commit();
|
||||
}
|
||||
|
||||
std::map<std::string, std::set<std::string>>
|
||||
RecursiveMap<std::string>
|
||||
UpnpDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag, TagType group) const
|
||||
ConstBuffer<TagType> tag_types) const
|
||||
{
|
||||
return ::CollectUniqueTags(*this, selection, tag, group);
|
||||
return ::CollectUniqueTags(*this, selection, tag_types);
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
|
@@ -94,7 +94,7 @@ UpdateService::CancelMount(const char *uri)
|
||||
cancel_current = next.IsDefined() && next.storage == storage2;
|
||||
}
|
||||
|
||||
if (auto *db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database)) {
|
||||
if (auto *db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get())) {
|
||||
queue.Erase(*db2);
|
||||
cancel_current |= next.IsDefined() && next.db == db2;
|
||||
}
|
||||
@@ -188,7 +188,7 @@ UpdateService::Enqueue(const char *path, bool discard)
|
||||
/* follow the mountpoint, update the mounted
|
||||
database */
|
||||
|
||||
db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database);
|
||||
db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get());
|
||||
if (db2 == nullptr)
|
||||
throw std::runtime_error("Cannot update this type of database");
|
||||
|
||||
|
@@ -493,6 +493,12 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
|
||||
if (!GetInfo(storage, "", info))
|
||||
return false;
|
||||
|
||||
if (!info.IsDirectory()) {
|
||||
FormatError(update_domain, "Not a directory: %s",
|
||||
storage.MapUTF8("").c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ExcludeList exclude_list;
|
||||
|
||||
UpdateDirectory(root, exclude_list, info);
|
||||
|
@@ -147,6 +147,18 @@ DecoderControl::Seek(SongTime t)
|
||||
seek_error = false;
|
||||
SynchronousCommandLocked(DecoderCommand::SEEK);
|
||||
|
||||
while (state == DecoderState::START)
|
||||
/* If the decoder falls back to DecoderState::START,
|
||||
this means that our SEEK command arrived too late,
|
||||
and the decoder had meanwhile finished decoding and
|
||||
went idle. Our SEEK command is finished, but that
|
||||
means only that the decoder thread has launched the
|
||||
decoder. To work around illegal states, we wait
|
||||
until the decoder plugin has become ready. This is
|
||||
a kludge, built on top of the "late seek" kludge.
|
||||
Not exactly elegant, sorry. */
|
||||
WaitForDecoder();
|
||||
|
||||
if (seek_error)
|
||||
throw std::runtime_error("Decoder failed to seek");
|
||||
}
|
||||
|
@@ -320,6 +320,11 @@ public:
|
||||
gcc_pure
|
||||
bool IsCurrentSong(const DetachedSong &_song) const noexcept;
|
||||
|
||||
gcc_pure
|
||||
bool IsUnseekableCurrentSong(const DetachedSong &_song) const noexcept {
|
||||
return !seekable && IsCurrentSong(_song);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept {
|
||||
return seekable && IsCurrentSong(_song);
|
||||
|
@@ -20,6 +20,8 @@
|
||||
#include "config.h"
|
||||
#include "DecoderList.hxx"
|
||||
#include "DecoderPlugin.hxx"
|
||||
#include "PluginUnavailable.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "config/Data.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "plugins/AudiofileDecoderPlugin.hxx"
|
||||
@@ -45,6 +47,7 @@
|
||||
#include "plugins/FluidsynthDecoderPlugin.hxx"
|
||||
#include "plugins/SidplayDecoderPlugin.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@@ -147,12 +150,22 @@ decoder_plugin_init_all(const ConfigData &config)
|
||||
if (param != nullptr)
|
||||
param->SetUsed();
|
||||
|
||||
if (plugin.Init(*param))
|
||||
decoder_plugins_enabled[i] = true;
|
||||
try {
|
||||
if (plugin.Init(*param))
|
||||
decoder_plugins_enabled[i] = true;
|
||||
} catch (const PluginUnavailable &e) {
|
||||
FormatError(e,
|
||||
"Decoder plugin '%s' is unavailable",
|
||||
plugin.name);
|
||||
} catch (...) {
|
||||
std::throw_with_nested(FormatRuntimeError("Failed to initialize decoder plugin '%s'",
|
||||
plugin.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decoder_plugin_deinit_all(void)
|
||||
void
|
||||
decoder_plugin_deinit_all() noexcept
|
||||
{
|
||||
decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
|
||||
plugin.Finish();
|
||||
|
@@ -40,11 +40,22 @@ decoder_plugin_init_all(const ConfigData &config);
|
||||
|
||||
/* this is where we "unload" all the "plugins" */
|
||||
void
|
||||
decoder_plugin_deinit_all();
|
||||
decoder_plugin_deinit_all() noexcept;
|
||||
|
||||
class ScopeDecoderPluginsInit {
|
||||
public:
|
||||
explicit ScopeDecoderPluginsInit(const ConfigData &config) {
|
||||
decoder_plugin_init_all(config);
|
||||
}
|
||||
|
||||
~ScopeDecoderPluginsInit() noexcept {
|
||||
decoder_plugin_deinit_all();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
static inline const DecoderPlugin *
|
||||
decoder_plugins_find(F f)
|
||||
decoder_plugins_find(F f) noexcept
|
||||
{
|
||||
for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
|
||||
if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
|
||||
|
@@ -455,6 +455,11 @@ static void
|
||||
decoder_run_song(DecoderControl &dc,
|
||||
const DetachedSong &song, const char *uri, Path path_fs)
|
||||
{
|
||||
if (dc.command == DecoderCommand::SEEK)
|
||||
/* if the SEEK command arrived too late, start the
|
||||
decoder at the seek position */
|
||||
dc.start_time = dc.seek_time;
|
||||
|
||||
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
|
||||
/* pass the song tag only if it's
|
||||
authoritative, i.e. if it's a local
|
||||
|
@@ -33,6 +33,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static constexpr Domain audiofile_domain("audiofile");
|
||||
|
||||
|
@@ -362,6 +362,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
unsigned channels, unsigned sample_rate,
|
||||
const offset_type total_bytes)
|
||||
{
|
||||
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||
const offset_type start_offset = is.GetOffset();
|
||||
|
||||
uint8_t buffer[8192];
|
||||
@@ -408,7 +409,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
bit_reverse_buffer(buffer, buffer + nbytes);
|
||||
|
||||
cmd = client.SubmitData(is, buffer, nbytes,
|
||||
sample_rate / 1000);
|
||||
kbit_rate);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -256,6 +256,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
offset_type n_blocks,
|
||||
bool bitreverse)
|
||||
{
|
||||
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||
const size_t block_size = channels * DSF_BLOCK_SIZE;
|
||||
const offset_type start_offset = is.GetOffset();
|
||||
|
||||
@@ -291,7 +292,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
|
||||
cmd = client.SubmitData(is,
|
||||
interleaved_buffer, block_size,
|
||||
sample_rate / 1000);
|
||||
kbit_rate);
|
||||
++i;
|
||||
}
|
||||
|
||||
|
@@ -39,8 +39,8 @@ InitHybridDsdDecoder(const ConfigBlock &block)
|
||||
without a DSD DAC, the PCM (=ALAC) part of the file is
|
||||
better */
|
||||
if (block.GetBlockParam("enabled") == nullptr) {
|
||||
LogInfo(hybrid_dsd_domain,
|
||||
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
|
||||
LogDebug(hybrid_dsd_domain,
|
||||
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -21,14 +21,13 @@
|
||||
#include "MadDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "tag/Id3Scan.hxx"
|
||||
#include "tag/Id3ReplayGain.hxx"
|
||||
#include "tag/Rva2.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/ReplayGain.hxx"
|
||||
#include "tag/MixRamp.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -40,8 +39,6 @@
|
||||
#include <id3tag.h>
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@@ -49,17 +46,17 @@
|
||||
|
||||
static constexpr unsigned long FRAMES_CUSHION = 2000;
|
||||
|
||||
enum mp3_action {
|
||||
DECODE_SKIP = -3,
|
||||
DECODE_BREAK = -2,
|
||||
DECODE_CONT = -1,
|
||||
DECODE_OK = 0
|
||||
enum class MadDecoderAction {
|
||||
SKIP,
|
||||
BREAK,
|
||||
CONT,
|
||||
OK
|
||||
};
|
||||
|
||||
enum muteframe {
|
||||
MUTEFRAME_NONE,
|
||||
MUTEFRAME_SKIP,
|
||||
MUTEFRAME_SEEK
|
||||
enum class MadDecoderMuteFrame {
|
||||
NONE,
|
||||
SKIP,
|
||||
SEEK
|
||||
};
|
||||
|
||||
/* the number of samples of silence the decoder inserts at start */
|
||||
@@ -79,7 +76,7 @@ ToSongTime(mad_timer_t t) noexcept
|
||||
}
|
||||
|
||||
static inline int32_t
|
||||
mad_fixed_to_24_sample(mad_fixed_t sample)
|
||||
mad_fixed_to_24_sample(mad_fixed_t sample) noexcept
|
||||
{
|
||||
static constexpr unsigned bits = 24;
|
||||
static constexpr mad_fixed_t MIN = -MAD_F_ONE;
|
||||
@@ -88,73 +85,79 @@ mad_fixed_to_24_sample(mad_fixed_t sample)
|
||||
/* round */
|
||||
sample = sample + (1L << (MAD_F_FRACBITS - bits));
|
||||
|
||||
/* clip */
|
||||
if (gcc_unlikely(sample > MAX))
|
||||
sample = MAX;
|
||||
else if (gcc_unlikely(sample < MIN))
|
||||
sample = MIN;
|
||||
|
||||
/* quantize */
|
||||
return sample >> (MAD_F_FRACBITS + 1 - bits);
|
||||
return Clamp(sample, MIN, MAX)
|
||||
>> (MAD_F_FRACBITS + 1 - bits);
|
||||
}
|
||||
|
||||
static void
|
||||
mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth,
|
||||
unsigned int start, unsigned int end,
|
||||
mad_fixed_to_24_buffer(int32_t *dest, const struct mad_pcm &src,
|
||||
size_t start, size_t end,
|
||||
unsigned int num_channels)
|
||||
{
|
||||
for (unsigned i = start; i < end; ++i)
|
||||
for (size_t i = start; i < end; ++i)
|
||||
for (unsigned c = 0; c < num_channels; ++c)
|
||||
*dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]);
|
||||
*dest++ = mad_fixed_to_24_sample(src.samples[c][i]);
|
||||
}
|
||||
|
||||
static bool
|
||||
mp3_plugin_init(const ConfigBlock &block)
|
||||
mad_plugin_init(const ConfigBlock &block)
|
||||
{
|
||||
gapless_playback = block.GetBlockValue("gapless",
|
||||
DEFAULT_GAPLESS_MP3_PLAYBACK);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct MadDecoder {
|
||||
class MadDecoder {
|
||||
static constexpr size_t READ_BUFFER_SIZE = 40960;
|
||||
static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048;
|
||||
|
||||
struct mad_stream stream;
|
||||
struct mad_frame frame;
|
||||
struct mad_synth synth;
|
||||
mad_timer_t timer;
|
||||
unsigned char input_buffer[READ_BUFFER_SIZE];
|
||||
int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE];
|
||||
int32_t output_buffer[sizeof(mad_pcm::samples) / sizeof(mad_fixed_t)];
|
||||
SignedSongTime total_time;
|
||||
SongTime elapsed_time;
|
||||
SongTime seek_time;
|
||||
enum muteframe mute_frame = MUTEFRAME_NONE;
|
||||
MadDecoderMuteFrame mute_frame = MadDecoderMuteFrame::NONE;
|
||||
long *frame_offsets = nullptr;
|
||||
mad_timer_t *times = nullptr;
|
||||
unsigned long highest_frame = 0;
|
||||
unsigned long max_frames = 0;
|
||||
unsigned long current_frame = 0;
|
||||
unsigned int drop_start_frames = 0;
|
||||
unsigned int drop_end_frames = 0;
|
||||
size_t highest_frame = 0;
|
||||
size_t max_frames = 0;
|
||||
size_t current_frame = 0;
|
||||
unsigned int drop_start_frames;
|
||||
unsigned int drop_end_frames;
|
||||
unsigned int drop_start_samples = 0;
|
||||
unsigned int drop_end_samples = 0;
|
||||
bool found_replay_gain = false;
|
||||
bool found_first_frame = false;
|
||||
bool decoded_first_frame = false;
|
||||
unsigned long bit_rate;
|
||||
|
||||
/**
|
||||
* If this flag is true, then end-of-file was seen and a
|
||||
* padding of 8 zero bytes were appended to #input_buffer, to
|
||||
* allow libmad to decode the last frame.
|
||||
*/
|
||||
bool was_eof = false;
|
||||
|
||||
DecoderClient *const client;
|
||||
InputStream &input_stream;
|
||||
enum mad_layer layer = mad_layer(0);
|
||||
|
||||
MadDecoder(DecoderClient *client, InputStream &input_stream);
|
||||
~MadDecoder();
|
||||
public:
|
||||
MadDecoder(DecoderClient *client, InputStream &input_stream) noexcept;
|
||||
~MadDecoder() noexcept;
|
||||
|
||||
bool Seek(long offset);
|
||||
bool FillBuffer();
|
||||
void ParseId3(size_t tagsize, Tag *tag);
|
||||
enum mp3_action DecodeNextFrameHeader(Tag *tag);
|
||||
enum mp3_action DecodeNextFrame();
|
||||
void RunDecoder() noexcept;
|
||||
bool RunScan(TagHandler &handler) noexcept;
|
||||
|
||||
private:
|
||||
bool Seek(long offset) noexcept;
|
||||
bool FillBuffer() noexcept;
|
||||
void ParseId3(size_t tagsize, Tag *tag) noexcept;
|
||||
MadDecoderAction DecodeNextFrameHeader(Tag *tag) noexcept;
|
||||
MadDecoderAction DecodeNextFrame() noexcept;
|
||||
|
||||
gcc_pure
|
||||
offset_type ThisFrameOffset() const noexcept;
|
||||
@@ -165,11 +168,11 @@ struct MadDecoder {
|
||||
/**
|
||||
* Attempt to calulcate the length of the song from filesize
|
||||
*/
|
||||
void FileSizeToSongLength();
|
||||
void FileSizeToSongLength() noexcept;
|
||||
|
||||
bool DecodeFirstFrame(Tag *tag);
|
||||
bool DecodeFirstFrame(Tag *tag) noexcept;
|
||||
|
||||
void AllocateBuffers() {
|
||||
void AllocateBuffers() noexcept {
|
||||
assert(max_frames > 0);
|
||||
assert(frame_offsets == nullptr);
|
||||
assert(times == nullptr);
|
||||
@@ -179,27 +182,39 @@ struct MadDecoder {
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
long TimeToFrame(SongTime t) const noexcept;
|
||||
size_t TimeToFrame(SongTime t) const noexcept;
|
||||
|
||||
void UpdateTimerNextFrame();
|
||||
/**
|
||||
* Record the current frame's offset in the "frame_offsets"
|
||||
* buffer and go forward to the next frame, updating the
|
||||
* attributes "current_frame" and "timer".
|
||||
*/
|
||||
void UpdateTimerNextFrame() noexcept;
|
||||
|
||||
/**
|
||||
* Sends the synthesized current frame via
|
||||
* DecoderClient::SubmitData().
|
||||
*/
|
||||
DecoderCommand SendPCM(unsigned i, unsigned pcm_length);
|
||||
DecoderCommand SubmitPCM(size_t start, size_t n) noexcept;
|
||||
|
||||
/**
|
||||
* Synthesize the current frame and send it via
|
||||
* DecoderClient::SubmitData().
|
||||
*/
|
||||
DecoderCommand SyncAndSend();
|
||||
DecoderCommand SynthAndSubmit() noexcept;
|
||||
|
||||
bool Read();
|
||||
/**
|
||||
* @return false to stop decoding
|
||||
*/
|
||||
bool HandleCurrentFrame() noexcept;
|
||||
|
||||
bool LoadNextFrame() noexcept;
|
||||
|
||||
bool Read() noexcept;
|
||||
};
|
||||
|
||||
MadDecoder::MadDecoder(DecoderClient *_client,
|
||||
InputStream &_input_stream)
|
||||
InputStream &_input_stream) noexcept
|
||||
:client(_client), input_stream(_input_stream)
|
||||
{
|
||||
mad_stream_init(&stream);
|
||||
@@ -210,7 +225,7 @@ MadDecoder::MadDecoder(DecoderClient *_client,
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::Seek(long offset)
|
||||
MadDecoder::Seek(long offset) noexcept
|
||||
{
|
||||
try {
|
||||
input_stream.LockSeek(offset);
|
||||
@@ -225,32 +240,38 @@ MadDecoder::Seek(long offset)
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::FillBuffer()
|
||||
MadDecoder::FillBuffer() noexcept
|
||||
{
|
||||
size_t remaining, length;
|
||||
unsigned char *dest;
|
||||
/* amount of rest data still residing in the buffer */
|
||||
size_t rest_size = 0;
|
||||
|
||||
size_t max_read_size = sizeof(input_buffer);
|
||||
unsigned char *dest = input_buffer;
|
||||
|
||||
if (stream.next_frame != nullptr) {
|
||||
remaining = stream.bufend - stream.next_frame;
|
||||
memmove(input_buffer, stream.next_frame, remaining);
|
||||
dest = input_buffer + remaining;
|
||||
length = READ_BUFFER_SIZE - remaining;
|
||||
} else {
|
||||
remaining = 0;
|
||||
length = READ_BUFFER_SIZE;
|
||||
dest = input_buffer;
|
||||
rest_size = stream.bufend - stream.next_frame;
|
||||
memmove(input_buffer, stream.next_frame, rest_size);
|
||||
dest += rest_size;
|
||||
max_read_size -= rest_size;
|
||||
}
|
||||
|
||||
/* we've exhausted the read buffer, so give up!, these potential
|
||||
* mp3 frames are way too big, and thus unlikely to be mp3 frames */
|
||||
if (length == 0)
|
||||
if (max_read_size == 0)
|
||||
return false;
|
||||
|
||||
length = decoder_read(client, input_stream, dest, length);
|
||||
if (length == 0)
|
||||
return false;
|
||||
size_t nbytes = decoder_read(client, input_stream,
|
||||
dest, max_read_size);
|
||||
if (nbytes == 0) {
|
||||
if (was_eof || max_read_size < MAD_BUFFER_GUARD)
|
||||
return false;
|
||||
|
||||
mad_stream_buffer(&stream, input_buffer, length + remaining);
|
||||
was_eof = true;
|
||||
nbytes = MAD_BUFFER_GUARD;
|
||||
memset(dest, 0, nbytes);
|
||||
}
|
||||
|
||||
mad_stream_buffer(&stream, input_buffer, rest_size + nbytes);
|
||||
stream.error = MAD_ERROR_NONE;
|
||||
|
||||
return true;
|
||||
@@ -286,7 +307,7 @@ parse_id3_mixramp(struct id3_tag *tag) noexcept
|
||||
#endif
|
||||
|
||||
inline void
|
||||
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag)
|
||||
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
||||
{
|
||||
#ifdef ENABLE_ID3TAG
|
||||
std::unique_ptr<id3_byte_t[]> allocated;
|
||||
@@ -354,7 +375,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag)
|
||||
* of the ID3 frame.
|
||||
*/
|
||||
static signed long
|
||||
id3_tag_query(const void *p0, size_t length)
|
||||
id3_tag_query(const void *p0, size_t length) noexcept
|
||||
{
|
||||
const char *p = (const char *)p0;
|
||||
|
||||
@@ -364,26 +385,26 @@ id3_tag_query(const void *p0, size_t length)
|
||||
}
|
||||
#endif /* !ENABLE_ID3TAG */
|
||||
|
||||
static enum mp3_action
|
||||
RecoverFrameError(struct mad_stream &stream)
|
||||
static MadDecoderAction
|
||||
RecoverFrameError(const struct mad_stream &stream) noexcept
|
||||
{
|
||||
if (MAD_RECOVERABLE(stream.error))
|
||||
return DECODE_SKIP;
|
||||
return MadDecoderAction::SKIP;
|
||||
else if (stream.error == MAD_ERROR_BUFLEN)
|
||||
return DECODE_CONT;
|
||||
return MadDecoderAction::CONT;
|
||||
|
||||
FormatWarning(mad_domain,
|
||||
"unrecoverable frame level error: %s",
|
||||
mad_stream_errorstr(&stream));
|
||||
return DECODE_BREAK;
|
||||
return MadDecoderAction::BREAK;
|
||||
}
|
||||
|
||||
enum mp3_action
|
||||
MadDecoder::DecodeNextFrameHeader(Tag *tag)
|
||||
MadDecoderAction
|
||||
MadDecoder::DecodeNextFrameHeader(Tag *tag) noexcept
|
||||
{
|
||||
if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
|
||||
!FillBuffer())
|
||||
return DECODE_BREAK;
|
||||
return MadDecoderAction::BREAK;
|
||||
|
||||
if (mad_header_decode(&frame.header, &stream)) {
|
||||
if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) {
|
||||
@@ -393,7 +414,7 @@ MadDecoder::DecodeNextFrameHeader(Tag *tag)
|
||||
|
||||
if (tagsize > 0) {
|
||||
ParseId3((size_t)tagsize, tag);
|
||||
return DECODE_CONT;
|
||||
return MadDecoderAction::CONT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,24 +425,24 @@ MadDecoder::DecodeNextFrameHeader(Tag *tag)
|
||||
if (layer == (mad_layer)0) {
|
||||
if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) {
|
||||
/* Only layer 2 and 3 have been tested to work */
|
||||
return DECODE_SKIP;
|
||||
return MadDecoderAction::SKIP;
|
||||
}
|
||||
|
||||
layer = new_layer;
|
||||
} else if (new_layer != layer) {
|
||||
/* Don't decode frames with a different layer than the first */
|
||||
return DECODE_SKIP;
|
||||
return MadDecoderAction::SKIP;
|
||||
}
|
||||
|
||||
return DECODE_OK;
|
||||
return MadDecoderAction::OK;
|
||||
}
|
||||
|
||||
enum mp3_action
|
||||
MadDecoder::DecodeNextFrame()
|
||||
MadDecoderAction
|
||||
MadDecoder::DecodeNextFrame() noexcept
|
||||
{
|
||||
if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
|
||||
!FillBuffer())
|
||||
return DECODE_BREAK;
|
||||
return MadDecoderAction::BREAK;
|
||||
|
||||
if (mad_frame_decode(&frame, &stream)) {
|
||||
if (stream.error == MAD_ERROR_LOSTSYNC) {
|
||||
@@ -430,14 +451,14 @@ MadDecoder::DecodeNextFrame()
|
||||
stream.this_frame);
|
||||
if (tagsize > 0) {
|
||||
mad_stream_skip(&stream, tagsize);
|
||||
return DECODE_CONT;
|
||||
return MadDecoderAction::CONT;
|
||||
}
|
||||
}
|
||||
|
||||
return RecoverFrameError(stream);
|
||||
}
|
||||
|
||||
return DECODE_OK;
|
||||
return MadDecoderAction::OK;
|
||||
}
|
||||
|
||||
/* xing stuff stolen from alsaplayer, and heavily modified by jat */
|
||||
@@ -476,7 +497,7 @@ struct lame {
|
||||
};
|
||||
|
||||
static bool
|
||||
parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
|
||||
parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) noexcept
|
||||
{
|
||||
int bitlen = *oldbitlen;
|
||||
|
||||
@@ -556,7 +577,7 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
|
||||
parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
|
||||
{
|
||||
/* Unlike the xing header, the lame tag has a fixed length. Fail if
|
||||
* not all 36 bytes (288 bits) are there. */
|
||||
@@ -647,7 +668,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
|
||||
}
|
||||
|
||||
static inline SongTime
|
||||
mp3_frame_duration(const struct mad_frame *frame)
|
||||
mad_frame_duration(const struct mad_frame *frame) noexcept
|
||||
{
|
||||
return ToSongTime(frame->header.duration);
|
||||
}
|
||||
@@ -672,12 +693,12 @@ MadDecoder::RestIncludingThisFrame() const noexcept
|
||||
}
|
||||
|
||||
inline void
|
||||
MadDecoder::FileSizeToSongLength()
|
||||
MadDecoder::FileSizeToSongLength() noexcept
|
||||
{
|
||||
if (input_stream.KnownSize()) {
|
||||
offset_type rest = RestIncludingThisFrame();
|
||||
|
||||
const SongTime frame_duration = mp3_frame_duration(&frame);
|
||||
const SongTime frame_duration = mad_frame_duration(&frame);
|
||||
const SongTime duration =
|
||||
SongTime::FromScale<uint64_t>(rest,
|
||||
frame.header.bitrate / 8);
|
||||
@@ -694,25 +715,30 @@ MadDecoder::FileSizeToSongLength()
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
|
||||
{
|
||||
struct xing xing;
|
||||
|
||||
#if GCC_CHECK_VERSION(10,0)
|
||||
/* work around bogus -Wuninitialized in GCC 10 */
|
||||
xing.frames = 0;
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
enum mp3_action ret;
|
||||
MadDecoderAction ret;
|
||||
do {
|
||||
ret = DecodeNextFrameHeader(tag);
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
if (ret == DECODE_SKIP) continue;
|
||||
if (ret == MadDecoderAction::SKIP) continue;
|
||||
|
||||
do {
|
||||
ret = DecodeNextFrame();
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
if (ret == DECODE_OK) break;
|
||||
if (ret == MadDecoderAction::OK) break;
|
||||
}
|
||||
|
||||
struct mad_bitptr ptr = stream.anc_ptr;
|
||||
@@ -724,7 +750,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
* if an xing tag exists, use that!
|
||||
*/
|
||||
if (parse_xing(&xing, &ptr, &bitlen)) {
|
||||
mute_frame = MUTEFRAME_SKIP;
|
||||
mute_frame = MadDecoderMuteFrame::SKIP;
|
||||
|
||||
if ((xing.flags & XING_FRAMES) && xing.frames) {
|
||||
mad_timer_t duration = frame.header.duration;
|
||||
@@ -736,9 +762,17 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
struct lame lame;
|
||||
if (parse_lame(&lame, &ptr, &bitlen)) {
|
||||
if (gapless_playback && input_stream.IsSeekable()) {
|
||||
/* libmad inserts 529 samples of
|
||||
silence at the beginning and
|
||||
removes those 529 samples at the
|
||||
end */
|
||||
drop_start_samples = lame.encoder_delay +
|
||||
DECODERDELAY;
|
||||
drop_end_samples = lame.encoder_padding;
|
||||
if (drop_end_samples > DECODERDELAY)
|
||||
drop_end_samples -= DECODERDELAY;
|
||||
else
|
||||
drop_end_samples = 0;
|
||||
}
|
||||
|
||||
/* Album gain isn't currently used. See comment in
|
||||
@@ -767,7 +801,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
return true;
|
||||
}
|
||||
|
||||
MadDecoder::~MadDecoder()
|
||||
MadDecoder::~MadDecoder() noexcept
|
||||
{
|
||||
mad_synth_finish(&synth);
|
||||
mad_frame_finish(&frame);
|
||||
@@ -777,10 +811,10 @@ MadDecoder::~MadDecoder()
|
||||
delete[] times;
|
||||
}
|
||||
|
||||
long
|
||||
size_t
|
||||
MadDecoder::TimeToFrame(SongTime t) const noexcept
|
||||
{
|
||||
unsigned long i;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < highest_frame; ++i) {
|
||||
auto frame_time = ToSongTime(times[i]);
|
||||
@@ -792,12 +826,11 @@ MadDecoder::TimeToFrame(SongTime t) const noexcept
|
||||
}
|
||||
|
||||
void
|
||||
MadDecoder::UpdateTimerNextFrame()
|
||||
MadDecoder::UpdateTimerNextFrame() noexcept
|
||||
{
|
||||
if (current_frame >= highest_frame) {
|
||||
/* record this frame's properties in frame_offsets
|
||||
(for seeking) and times */
|
||||
bit_rate = frame.header.bitrate;
|
||||
|
||||
if (current_frame >= max_frames)
|
||||
/* cap current_frame */
|
||||
@@ -818,36 +851,22 @@ MadDecoder::UpdateTimerNextFrame()
|
||||
}
|
||||
|
||||
DecoderCommand
|
||||
MadDecoder::SendPCM(unsigned i, unsigned pcm_length)
|
||||
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
|
||||
{
|
||||
unsigned max_samples = sizeof(output_buffer) /
|
||||
sizeof(output_buffer[0]) /
|
||||
MAD_NCHANNELS(&frame.header);
|
||||
size_t num_samples = pcm_length - i;
|
||||
|
||||
while (i < pcm_length) {
|
||||
unsigned int num_samples = pcm_length - i;
|
||||
if (num_samples > max_samples)
|
||||
num_samples = max_samples;
|
||||
mad_fixed_to_24_buffer(output_buffer, synth.pcm,
|
||||
i, i + num_samples,
|
||||
MAD_NCHANNELS(&frame.header));
|
||||
num_samples *= MAD_NCHANNELS(&frame.header);
|
||||
|
||||
i += num_samples;
|
||||
|
||||
mad_fixed_to_24_buffer(output_buffer, &synth,
|
||||
i - num_samples, i,
|
||||
MAD_NCHANNELS(&frame.header));
|
||||
num_samples *= MAD_NCHANNELS(&frame.header);
|
||||
|
||||
auto cmd = client->SubmitData(input_stream, output_buffer,
|
||||
sizeof(output_buffer[0]) * num_samples,
|
||||
bit_rate / 1000);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
}
|
||||
|
||||
return DecoderCommand::NONE;
|
||||
return client->SubmitData(input_stream, output_buffer,
|
||||
sizeof(output_buffer[0]) * num_samples,
|
||||
frame.header.bitrate / 1000);
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MadDecoder::SyncAndSend()
|
||||
MadDecoder::SynthAndSubmit() noexcept
|
||||
{
|
||||
mad_synth_frame(&synth, &frame);
|
||||
|
||||
@@ -864,33 +883,33 @@ MadDecoder::SyncAndSend()
|
||||
drop_start_frames--;
|
||||
return DecoderCommand::NONE;
|
||||
} else if ((drop_end_frames > 0) &&
|
||||
(current_frame == (max_frames + 1 - drop_end_frames))) {
|
||||
current_frame == max_frames - drop_end_frames) {
|
||||
/* stop decoding, effectively dropping all remaining
|
||||
frames */
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
unsigned i = 0;
|
||||
size_t i = 0;
|
||||
if (!decoded_first_frame) {
|
||||
i = drop_start_samples;
|
||||
decoded_first_frame = true;
|
||||
}
|
||||
|
||||
unsigned pcm_length = synth.pcm.length;
|
||||
size_t pcm_length = synth.pcm.length;
|
||||
if (drop_end_samples &&
|
||||
(current_frame == max_frames - drop_end_frames)) {
|
||||
current_frame == max_frames - drop_end_frames - 1) {
|
||||
if (drop_end_samples >= pcm_length)
|
||||
pcm_length = 0;
|
||||
else
|
||||
pcm_length -= drop_end_samples;
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
pcm_length -= drop_end_samples;
|
||||
}
|
||||
|
||||
auto cmd = SendPCM(i, pcm_length);
|
||||
auto cmd = SubmitPCM(i, pcm_length);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
|
||||
if (drop_end_samples &&
|
||||
(current_frame == max_frames - drop_end_frames))
|
||||
current_frame == max_frames - drop_end_frames - 1)
|
||||
/* stop decoding, effectively dropping
|
||||
* all remaining samples */
|
||||
return DecoderCommand::STOP;
|
||||
@@ -899,44 +918,51 @@ MadDecoder::SyncAndSend()
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::Read()
|
||||
MadDecoder::HandleCurrentFrame() noexcept
|
||||
{
|
||||
UpdateTimerNextFrame();
|
||||
|
||||
switch (mute_frame) {
|
||||
DecoderCommand cmd;
|
||||
|
||||
case MUTEFRAME_SKIP:
|
||||
mute_frame = MUTEFRAME_NONE;
|
||||
case MadDecoderMuteFrame::SKIP:
|
||||
mute_frame = MadDecoderMuteFrame::NONE;
|
||||
break;
|
||||
case MUTEFRAME_SEEK:
|
||||
case MadDecoderMuteFrame::SEEK:
|
||||
if (elapsed_time >= seek_time)
|
||||
mute_frame = MUTEFRAME_NONE;
|
||||
mute_frame = MadDecoderMuteFrame::NONE;
|
||||
UpdateTimerNextFrame();
|
||||
break;
|
||||
case MUTEFRAME_NONE:
|
||||
cmd = SyncAndSend();
|
||||
case MadDecoderMuteFrame::NONE:
|
||||
cmd = SynthAndSubmit();
|
||||
UpdateTimerNextFrame();
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
assert(input_stream.IsSeekable());
|
||||
|
||||
const auto t = client->GetSeekTime();
|
||||
unsigned long j = TimeToFrame(t);
|
||||
size_t j = TimeToFrame(t);
|
||||
if (j < highest_frame) {
|
||||
if (Seek(frame_offsets[j])) {
|
||||
current_frame = j;
|
||||
was_eof = false;
|
||||
client->CommandFinished();
|
||||
} else
|
||||
client->SeekError();
|
||||
} else {
|
||||
seek_time = t;
|
||||
mute_frame = MUTEFRAME_SEEK;
|
||||
mute_frame = MadDecoderMuteFrame::SEEK;
|
||||
client->CommandFinished();
|
||||
}
|
||||
} else if (cmd != DecoderCommand::NONE)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::LoadNextFrame() noexcept
|
||||
{
|
||||
while (true) {
|
||||
enum mp3_action ret;
|
||||
MadDecoderAction ret;
|
||||
do {
|
||||
Tag tag;
|
||||
|
||||
@@ -945,84 +971,104 @@ MadDecoder::Read()
|
||||
if (!tag.IsEmpty())
|
||||
client->SubmitTag(input_stream,
|
||||
std::move(tag));
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
|
||||
const bool skip = ret == DECODE_SKIP;
|
||||
const bool skip = ret == MadDecoderAction::SKIP;
|
||||
|
||||
if (mute_frame == MUTEFRAME_NONE) {
|
||||
if (mute_frame == MadDecoderMuteFrame::NONE) {
|
||||
do {
|
||||
ret = DecodeNextFrame();
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!skip && ret == DECODE_OK)
|
||||
if (!skip && ret == MadDecoderAction::OK)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mp3_decode(DecoderClient &client, InputStream &input_stream)
|
||||
inline bool
|
||||
MadDecoder::Read() noexcept
|
||||
{
|
||||
MadDecoder data(&client, input_stream);
|
||||
return HandleCurrentFrame() &&
|
||||
LoadNextFrame();
|
||||
}
|
||||
|
||||
inline void
|
||||
MadDecoder::RunDecoder() noexcept
|
||||
{
|
||||
assert(client != nullptr);
|
||||
|
||||
Tag tag;
|
||||
if (!data.DecodeFirstFrame(&tag)) {
|
||||
if (client.GetCommand() == DecoderCommand::NONE)
|
||||
if (!DecodeFirstFrame(&tag)) {
|
||||
if (client->GetCommand() == DecoderCommand::NONE)
|
||||
LogError(mad_domain,
|
||||
"input/Input does not appear to be a mp3 bit stream");
|
||||
"input does not appear to be a mp3 bit stream");
|
||||
return;
|
||||
}
|
||||
|
||||
data.AllocateBuffers();
|
||||
AllocateBuffers();
|
||||
|
||||
client.Ready(CheckAudioFormat(data.frame.header.samplerate,
|
||||
SampleFormat::S24_P32,
|
||||
MAD_NCHANNELS(&data.frame.header)),
|
||||
input_stream.IsSeekable(),
|
||||
data.total_time);
|
||||
client->Ready(CheckAudioFormat(frame.header.samplerate,
|
||||
SampleFormat::S24_P32,
|
||||
MAD_NCHANNELS(&frame.header)),
|
||||
input_stream.IsSeekable(),
|
||||
total_time);
|
||||
|
||||
if (!tag.IsEmpty())
|
||||
client.SubmitTag(input_stream, std::move(tag));
|
||||
client->SubmitTag(input_stream, std::move(tag));
|
||||
|
||||
while (data.Read()) {}
|
||||
while (Read()) {}
|
||||
}
|
||||
|
||||
static bool
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
static void
|
||||
mad_decode(DecoderClient &client, InputStream &input_stream)
|
||||
{
|
||||
MadDecoder data(nullptr, is);
|
||||
if (!data.DecodeFirstFrame(nullptr))
|
||||
MadDecoder data(&client, input_stream);
|
||||
data.RunDecoder();
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::RunScan(TagHandler &handler) noexcept
|
||||
{
|
||||
if (!DecodeFirstFrame(nullptr))
|
||||
return false;
|
||||
|
||||
if (!data.total_time.IsNegative())
|
||||
handler.OnDuration(SongTime(data.total_time));
|
||||
if (!total_time.IsNegative())
|
||||
handler.OnDuration(SongTime(total_time));
|
||||
|
||||
try {
|
||||
handler.OnAudioFormat(CheckAudioFormat(data.frame.header.samplerate,
|
||||
handler.OnAudioFormat(CheckAudioFormat(frame.header.samplerate,
|
||||
SampleFormat::S24_P32,
|
||||
MAD_NCHANNELS(&data.frame.header)));
|
||||
MAD_NCHANNELS(&frame.header)));
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr };
|
||||
static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr };
|
||||
static bool
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
{
|
||||
MadDecoder data(nullptr, is);
|
||||
return data.RunScan(handler);
|
||||
}
|
||||
|
||||
static const char *const mad_suffixes[] = { "mp3", "mp2", nullptr };
|
||||
static const char *const mad_mime_types[] = { "audio/mpeg", nullptr };
|
||||
|
||||
const struct DecoderPlugin mad_decoder_plugin = {
|
||||
"mad",
|
||||
mp3_plugin_init,
|
||||
mad_plugin_init,
|
||||
nullptr,
|
||||
mp3_decode,
|
||||
mad_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mad_decoder_scan_stream,
|
||||
nullptr,
|
||||
mp3_suffixes,
|
||||
mp3_mime_types,
|
||||
mad_suffixes,
|
||||
mad_mime_types,
|
||||
};
|
||||
|
@@ -137,6 +137,28 @@ mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
|
||||
*dest++ = mpc_to_mpd_sample(*src++);
|
||||
}
|
||||
|
||||
static constexpr ReplayGainTuple
|
||||
ImportMpcdecReplayGain(mpc_uint16_t gain, mpc_uint16_t peak) noexcept
|
||||
{
|
||||
auto t = ReplayGainTuple::Undefined();
|
||||
|
||||
if (gain != 0 && peak != 0) {
|
||||
t.gain = MPC_OLD_GAIN_REF - (gain / 256.);
|
||||
t.peak = pow(10, peak / 256. / 20) / 32767;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static constexpr ReplayGainInfo
|
||||
ImportMpcdecReplayGain(const mpc_streaminfo &info) noexcept
|
||||
{
|
||||
auto rgi = ReplayGainInfo::Undefined();
|
||||
rgi.album = ImportMpcdecReplayGain(info.gain_album, info.peak_album);
|
||||
rgi.track = ImportMpcdecReplayGain(info.gain_title, info.peak_title);
|
||||
return rgi;
|
||||
}
|
||||
|
||||
static void
|
||||
mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
{
|
||||
@@ -167,14 +189,11 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
mpcdec_sample_format,
|
||||
info.channels);
|
||||
|
||||
ReplayGainInfo rgi;
|
||||
rgi.Clear();
|
||||
rgi.album.gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
|
||||
rgi.album.peak = pow(10, info.peak_album / 256. / 20) / 32767;
|
||||
rgi.track.gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
|
||||
rgi.track.peak = pow(10, info.peak_title / 256. / 20) / 32767;
|
||||
|
||||
client.SubmitReplayGain(&rgi);
|
||||
{
|
||||
const auto rgi = ImportMpcdecReplayGain(info);
|
||||
if (rgi.IsDefined())
|
||||
client.SubmitReplayGain(&rgi);
|
||||
}
|
||||
|
||||
client.Ready(audio_format, is.IsSeekable(),
|
||||
SongTime::FromS(mpc_streaminfo_get_length(&info)));
|
||||
|
@@ -208,10 +208,12 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
||||
TagBuilder tag_builder;
|
||||
AddTagHandler h(tag_builder);
|
||||
|
||||
if (ScanOpusTags(packet.packet, packet.bytes, &rgi, h) &&
|
||||
!tag_builder.empty()) {
|
||||
client.SubmitReplayGain(&rgi);
|
||||
if (!ScanOpusTags(packet.packet, packet.bytes, &rgi, h))
|
||||
return;
|
||||
|
||||
client.SubmitReplayGain(&rgi);
|
||||
|
||||
if (!tag_builder.empty()) {
|
||||
Tag tag = tag_builder.Commit();
|
||||
auto cmd = client.SubmitTag(input_stream, std::move(tag));
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -20,6 +20,8 @@
|
||||
#ifndef MPD_OPUS_READER_HXX
|
||||
#define MPD_OPUS_READER_HXX
|
||||
|
||||
#include "util/StringView.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -81,18 +83,16 @@ public:
|
||||
return ReadWord(length) && Skip(length);
|
||||
}
|
||||
|
||||
char *ReadString() {
|
||||
StringView ReadString() {
|
||||
uint32_t length;
|
||||
if (!ReadWord(length) || length >= 65536)
|
||||
if (!ReadWord(length))
|
||||
return nullptr;
|
||||
|
||||
const char *src = (const char *)Read(length);
|
||||
if (src == nullptr)
|
||||
return nullptr;
|
||||
|
||||
char *dest = new char[length + 1];
|
||||
*std::copy_n(src, length, dest) = 0;
|
||||
return dest;
|
||||
return {src, length};
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -22,10 +22,12 @@
|
||||
#include "lib/xiph/XiphTags.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/ParseName.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "ReplayGainInfo.hxx"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
gcc_pure
|
||||
@@ -44,7 +46,7 @@ ScanOneOpusTag(const char *name, const char *value,
|
||||
ReplayGainInfo *rgi,
|
||||
TagHandler &handler) noexcept
|
||||
{
|
||||
if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) {
|
||||
if (rgi != nullptr && StringEqualsCaseASCII(name, "R128_TRACK_GAIN")) {
|
||||
/* R128_TRACK_GAIN is a Q7.8 fixed point number in
|
||||
dB */
|
||||
|
||||
@@ -52,7 +54,8 @@ ScanOneOpusTag(const char *name, const char *value,
|
||||
long l = strtol(value, &endptr, 10);
|
||||
if (endptr > value && *endptr == 0)
|
||||
rgi->track.gain = double(l) / 256.;
|
||||
} else if (rgi != nullptr && strcmp(name, "R128_ALBUM_GAIN") == 0) {
|
||||
} else if (rgi != nullptr &&
|
||||
StringEqualsCaseASCII(name, "R128_ALBUM_GAIN")) {
|
||||
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
|
||||
dB */
|
||||
|
||||
@@ -91,18 +94,25 @@ ScanOpusTags(const void *data, size_t size,
|
||||
return false;
|
||||
|
||||
while (n-- > 0) {
|
||||
char *p = r.ReadString();
|
||||
if (p == nullptr)
|
||||
const auto s = r.ReadString();
|
||||
if (s == nullptr)
|
||||
return false;
|
||||
|
||||
char *eq = strchr(p, '=');
|
||||
if (eq != nullptr && eq > p) {
|
||||
*eq = 0;
|
||||
if (s.size >= 4096)
|
||||
continue;
|
||||
|
||||
ScanOneOpusTag(p, eq + 1, rgi, handler);
|
||||
}
|
||||
const auto eq = s.Find('=');
|
||||
if (eq == nullptr || eq == s.data)
|
||||
continue;
|
||||
|
||||
delete[] p;
|
||||
auto name = s, value = s;
|
||||
name.SetEnd(eq);
|
||||
value.MoveFront(eq + 1);
|
||||
|
||||
const std::string name2(name.data, name.size);
|
||||
const std::string value2(value.data, value.size);
|
||||
|
||||
ScanOneOpusTag(name2.c_str(), value2.c_str(), rgi, handler);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "lib/icu/Converter.hxx"
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#include "fs/io/FileReader.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@@ -32,6 +33,8 @@
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -432,19 +435,70 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
}
|
||||
|
||||
static AllocatedString<char>
|
||||
Windows1252ToUTF8(const char *s) noexcept
|
||||
{
|
||||
#ifdef HAVE_ICU_CONVERTER
|
||||
try {
|
||||
std::unique_ptr<IcuConverter>
|
||||
converter(IcuConverter::Create("windows-1252"));
|
||||
|
||||
return converter->ToUTF8(s);
|
||||
} catch (...) { }
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Fallback to not transcoding windows-1252 to utf-8, that may result
|
||||
* in invalid utf-8 unless nonprintable characters are replaced.
|
||||
*/
|
||||
auto t = AllocatedString<char>::Duplicate(s);
|
||||
|
||||
for (size_t i = 0; t[i] != AllocatedString<char>::SENTINEL; i++)
|
||||
if (!IsPrintableASCII(t[i]))
|
||||
t[i] = '?';
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const char *
|
||||
static AllocatedString<char>
|
||||
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
|
||||
{
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
return info.numberOfInfoStrings() > i
|
||||
const char *s = info.numberOfInfoStrings() > i
|
||||
? info.infoString(i)
|
||||
: nullptr;
|
||||
: "";
|
||||
#else
|
||||
return info.numberOfInfoStrings > i
|
||||
const char *s = info.numberOfInfoStrings > i
|
||||
? info.infoString[i]
|
||||
: nullptr;
|
||||
: "";
|
||||
#endif
|
||||
|
||||
return Windows1252ToUTF8(s);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static AllocatedString<char>
|
||||
GetDateString(const SidTuneInfo &info) noexcept
|
||||
{
|
||||
/*
|
||||
* Field 2 is called <released>, previously used as <copyright>.
|
||||
* It is formatted <year><space><company or author or group>,
|
||||
* where <year> may be <YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for
|
||||
* example "1987", "199?", "19??" or "1985-87". The <company or
|
||||
* author or group> may be for example Rob Hubbard. A full field
|
||||
* may be for example "1987 Rob Hubbard".
|
||||
*/
|
||||
AllocatedString<char> release = GetInfoString(info, 2);
|
||||
|
||||
/* Keep the <year> part only for the date. */
|
||||
for (size_t i = 0; release[i] != AllocatedString<char>::SENTINEL; i++)
|
||||
if (std::isspace(release[i])) {
|
||||
release[i] = AllocatedString<char>::SENTINEL;
|
||||
break;
|
||||
}
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -452,27 +506,25 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
|
||||
TagHandler &handler) noexcept
|
||||
{
|
||||
/* title */
|
||||
const char *title = GetInfoString(info, 0);
|
||||
if (title == nullptr)
|
||||
title = "";
|
||||
const auto title = GetInfoString(info, 0);
|
||||
|
||||
if (n_tracks > 1) {
|
||||
const auto tag_title =
|
||||
StringFormat<1024>("%s (%u/%u)",
|
||||
title, track, n_tracks);
|
||||
handler.OnTag(TAG_TITLE, tag_title);
|
||||
title.c_str(), track, n_tracks);
|
||||
handler.OnTag(TAG_TITLE, tag_title.c_str());
|
||||
} else
|
||||
handler.OnTag(TAG_TITLE, title);
|
||||
handler.OnTag(TAG_TITLE, title.c_str());
|
||||
|
||||
/* artist */
|
||||
const char *artist = GetInfoString(info, 1);
|
||||
if (artist != nullptr)
|
||||
handler.OnTag(TAG_ARTIST, artist);
|
||||
const auto artist = GetInfoString(info, 1);
|
||||
if (!artist.empty())
|
||||
handler.OnTag(TAG_ARTIST, artist.c_str());
|
||||
|
||||
/* date */
|
||||
const char *date = GetInfoString(info, 2);
|
||||
if (date != nullptr)
|
||||
handler.OnTag(TAG_DATE, date);
|
||||
const auto date = GetDateString(info);
|
||||
if (!date.empty())
|
||||
handler.OnTag(TAG_DATE, date.c_str());
|
||||
|
||||
/* track */
|
||||
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track));
|
||||
@@ -547,6 +599,10 @@ sidplay_container_scan(Path path_fs)
|
||||
AddTagHandler h(tag_builder);
|
||||
ScanSidTuneInfo(info, i, n_tracks, h);
|
||||
|
||||
const SignedSongTime duration = get_song_length(tune);
|
||||
if (!duration.IsNegative())
|
||||
h.OnDuration(SongTime(duration));
|
||||
|
||||
char track_name[32];
|
||||
/* Construct container/tune path names, eg.
|
||||
Delta.sid/tune_001.sid */
|
||||
|
@@ -20,18 +20,18 @@
|
||||
#include "WildmidiDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "PluginUnavailable.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <wildmidi_lib.h>
|
||||
}
|
||||
|
||||
static constexpr Domain wildmidi_domain("wildmidi");
|
||||
|
||||
static constexpr AudioFormat wildmidi_audio_format{48000, SampleFormat::S16, 2};
|
||||
|
||||
static bool
|
||||
@@ -43,14 +43,27 @@ wildmidi_init(const ConfigBlock &block)
|
||||
|
||||
if (!FileExists(path)) {
|
||||
const auto utf8 = path.ToUTF8();
|
||||
FormatDebug(wildmidi_domain,
|
||||
"configuration file does not exist: %s",
|
||||
utf8.c_str());
|
||||
return false;
|
||||
throw PluginUnavailable(StringFormat<1024>("configuration file does not exist: %s",
|
||||
utf8.c_str()));
|
||||
}
|
||||
|
||||
return WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
|
||||
0) == 0;
|
||||
#ifdef LIBWILDMIDI_VERSION
|
||||
/* WildMidi_ClearError() requires libwildmidi 0.4 */
|
||||
WildMidi_ClearError();
|
||||
AtScopeExit() { WildMidi_ClearError(); };
|
||||
#endif
|
||||
|
||||
if (WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
|
||||
0) != 0) {
|
||||
#ifdef LIBWILDMIDI_VERSION
|
||||
/* WildMidi_GetError() requires libwildmidi 0.4 */
|
||||
throw PluginUnavailable(WildMidi_GetError());
|
||||
#else
|
||||
throw PluginUnavailable("WildMidi_Init() failed");
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -36,8 +36,9 @@ if flac_dep.found()
|
||||
]
|
||||
endif
|
||||
|
||||
conf.set('ENABLE_VORBIS_DECODER', libvorbis_dep.found())
|
||||
if libvorbis_dep.found()
|
||||
conf.set('ENABLE_VORBIS_DECODER', libvorbis_dep.found() or libvorbisidec_dep.found())
|
||||
conf.set('HAVE_TREMOR', libvorbisidec_dep.found())
|
||||
if libvorbis_dep.found() or libvorbisidec_dep.found()
|
||||
decoder_plugins_sources += [
|
||||
'VorbisDecoderPlugin.cxx',
|
||||
'VorbisDomain.cxx',
|
||||
@@ -181,6 +182,7 @@ decoder_plugins = static_library(
|
||||
libsidplay_dep,
|
||||
libsndfile_dep,
|
||||
libvorbis_dep,
|
||||
libvorbisidec_dep,
|
||||
ogg_dep,
|
||||
wavpack_dep,
|
||||
wildmidi_dep,
|
||||
|
@@ -110,15 +110,9 @@ BufferedSocket::OnSocketReady(unsigned flags) noexcept
|
||||
if (flags & READ) {
|
||||
assert(!input.IsFull());
|
||||
|
||||
if (!ReadToBuffer())
|
||||
if (!ReadToBuffer() || !ResumeInput())
|
||||
return false;
|
||||
|
||||
if (!ResumeInput())
|
||||
/* we must return "true" here or
|
||||
SocketMonitor::Dispatch() will call
|
||||
Cancel() on a freed object */
|
||||
return true;
|
||||
|
||||
if (!input.IsFull())
|
||||
ScheduleRead();
|
||||
}
|
||||
|
@@ -46,6 +46,11 @@ public:
|
||||
using SocketMonitor::Close;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @return the number of bytes read from the socket, 0 if the
|
||||
* socket isn't ready for reading, -1 on error (the socket has
|
||||
* been closed and probably destructed)
|
||||
*/
|
||||
ssize_t DirectRead(void *data, size_t length) noexcept;
|
||||
|
||||
/**
|
||||
|
@@ -80,7 +80,7 @@ private:
|
||||
void
|
||||
BlockingCall(EventLoop &loop, std::function<void()> &&f)
|
||||
{
|
||||
if (loop.IsDead() || loop.IsInside()) {
|
||||
if (!loop.IsAlive() || loop.IsInside()) {
|
||||
/* we're already inside the loop - we can simply call
|
||||
the function */
|
||||
f();
|
||||
|
@@ -45,6 +45,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @return the number of bytes written to the socket, 0 if the
|
||||
* socket isn't ready for writing, -1 on error (the socket has
|
||||
* been closed and probably destructed)
|
||||
*/
|
||||
ssize_t DirectWrite(const void *data, size_t length) noexcept;
|
||||
|
||||
protected:
|
||||
|
@@ -25,7 +25,13 @@
|
||||
|
||||
EventLoop::EventLoop(ThreadId _thread)
|
||||
:SocketMonitor(*this),
|
||||
quit(false), dead(false),
|
||||
/* if this instance is hosted by an EventThread (no ThreadId
|
||||
known yet) then we're not yet alive until the thread is
|
||||
started; for the main EventLoop instance, we assume it's
|
||||
already alive, because nobody but EventThread will call
|
||||
SetAlive() */
|
||||
alive(!_thread.IsNull()),
|
||||
quit(false),
|
||||
thread(_thread)
|
||||
{
|
||||
SocketMonitor::Open(SocketDescriptor(wake_fd.Get()));
|
||||
@@ -131,7 +137,8 @@ static constexpr int
|
||||
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
|
||||
{
|
||||
return timeout >= timeout.zero()
|
||||
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count())
|
||||
/* round up (+1) to avoid unnecessary wakeups */
|
||||
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()) + 1
|
||||
: -1;
|
||||
}
|
||||
|
||||
@@ -143,12 +150,11 @@ EventLoop::Run() noexcept
|
||||
|
||||
assert(IsInside());
|
||||
assert(!quit);
|
||||
assert(!dead);
|
||||
assert(alive);
|
||||
assert(busy);
|
||||
|
||||
SocketMonitor::Schedule(SocketMonitor::READ);
|
||||
AtScopeExit(this) {
|
||||
dead = true;
|
||||
SocketMonitor::Cancel();
|
||||
};
|
||||
|
||||
@@ -215,8 +221,6 @@ EventLoop::Run() noexcept
|
||||
} while (!quit);
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert(!dead);
|
||||
assert(busy);
|
||||
assert(thread.IsInside());
|
||||
#endif
|
||||
}
|
||||
|
@@ -85,12 +85,15 @@ class EventLoop final : SocketMonitor
|
||||
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
|
||||
std::atomic_bool quit;
|
||||
|
||||
/**
|
||||
* If this is true, then Run() has returned.
|
||||
* Is this #EventLoop alive, i.e. can events be scheduled?
|
||||
* This is used by BlockingCall() to determine whether
|
||||
* schedule in the #EventThread or to call directly (if
|
||||
* there's no #EventThread yet/anymore).
|
||||
*/
|
||||
std::atomic_bool dead;
|
||||
bool alive;
|
||||
|
||||
std::atomic_bool quit;
|
||||
|
||||
/**
|
||||
* True when the object has been modified and another check is
|
||||
@@ -207,9 +210,12 @@ private:
|
||||
bool OnSocketReady(unsigned flags) noexcept override;
|
||||
|
||||
public:
|
||||
gcc_pure
|
||||
bool IsDead() const noexcept {
|
||||
return dead;
|
||||
void SetAlive(bool _alive) noexcept {
|
||||
alive = _alive;
|
||||
}
|
||||
|
||||
bool IsAlive() const noexcept {
|
||||
return alive;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user