Compare commits
157 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
df4b6b92f2 | ||
![]() |
1c69913eca | ||
![]() |
cb5c6259fd | ||
![]() |
bf287fefb5 | ||
![]() |
20bf1d68e6 | ||
![]() |
9bc4c168fd | ||
![]() |
3415049d1c | ||
![]() |
a45949b597 | ||
![]() |
6009d4abab | ||
![]() |
16fb843c9b | ||
![]() |
36b333459b | ||
![]() |
4d3320233e | ||
![]() |
933a1a41e6 | ||
![]() |
1ff8626716 | ||
![]() |
c30466b84a | ||
![]() |
868f1a4431 | ||
![]() |
05f529fffd | ||
![]() |
f01388559f | ||
![]() |
27edd4a610 | ||
![]() |
cc421b04cd | ||
![]() |
3f2bc325a1 | ||
![]() |
54686dfd79 | ||
![]() |
f22cf02ed8 | ||
![]() |
5b51d0f733 | ||
![]() |
e03f82636a | ||
![]() |
d53d85bd79 | ||
![]() |
4682ae0898 | ||
![]() |
fd5b195879 | ||
![]() |
bb5df9839d | ||
![]() |
be34d55291 | ||
![]() |
c13911b171 | ||
![]() |
6f83bdd6f3 | ||
![]() |
9bcd425a85 | ||
![]() |
ec917f70d2 | ||
![]() |
40ce4eeb43 | ||
![]() |
29ae84e199 | ||
![]() |
250011f016 | ||
![]() |
e08c85ae2d | ||
![]() |
dcb5ca203c | ||
![]() |
77df5a8f24 | ||
![]() |
d6bebd2507 | ||
![]() |
f74996c02f | ||
![]() |
eea2d35d3a | ||
![]() |
d94e8bd82d | ||
![]() |
b0c92e1a34 | ||
![]() |
ead5bcf048 | ||
![]() |
bdd268a524 | ||
![]() |
e783c2bd2c | ||
![]() |
837fc98638 | ||
![]() |
5deca66fdc | ||
![]() |
cfe2dd4147 | ||
![]() |
00f8d65a17 | ||
![]() |
4e0e4c00bf | ||
![]() |
a8c77a6fba | ||
![]() |
31aa6d0c4f | ||
![]() |
d051c4931d | ||
![]() |
94b0baceb0 | ||
![]() |
16feb261e2 | ||
![]() |
f084bf7872 | ||
![]() |
1112d3907a | ||
![]() |
3464497880 | ||
![]() |
651f57bced | ||
![]() |
b4e72aba6c | ||
![]() |
061dd2dfef | ||
![]() |
5f4ec7de5b | ||
![]() |
6f81bb4b09 | ||
![]() |
4ed60a5711 | ||
![]() |
c93195c94b | ||
![]() |
f30adac4bb | ||
![]() |
a4e4217204 | ||
![]() |
8754d705a1 | ||
![]() |
23d4a2d6a5 | ||
![]() |
ce77b148d9 | ||
![]() |
be3eca39e8 | ||
![]() |
3413b1aeb4 | ||
![]() |
356d13e9dd | ||
![]() |
fa34bf0aaf | ||
![]() |
5d0941476a | ||
![]() |
5ff0bbd0f8 | ||
![]() |
a3764e533c | ||
![]() |
3e05cba30e | ||
![]() |
14b3c0f0af | ||
![]() |
67aff05051 | ||
![]() |
19a101c3ac | ||
![]() |
8da17a8211 | ||
![]() |
2748929039 | ||
![]() |
0c900a4bfa | ||
![]() |
f1d5d70010 | ||
![]() |
56ebc7637d | ||
![]() |
996dd9fc8b | ||
![]() |
056514d598 | ||
![]() |
9a21bdfd6a | ||
![]() |
03f99dd26e | ||
![]() |
bfb1b641f9 | ||
![]() |
72ba98c464 | ||
![]() |
dcd19c0592 | ||
![]() |
109159e0f7 | ||
![]() |
409b877eea | ||
![]() |
c5bf7948ff | ||
![]() |
b9f7127691 | ||
![]() |
1e6f5f012c | ||
![]() |
225d85fd9b | ||
![]() |
1bb22f118d | ||
![]() |
552c30eae4 | ||
![]() |
48e8a26813 | ||
![]() |
ade847bc89 | ||
![]() |
a6173e0eae | ||
![]() |
4529bb4a83 | ||
![]() |
258ecb764f | ||
![]() |
6f595e9abb | ||
![]() |
35c4c7e8bf | ||
![]() |
293ed924d1 | ||
![]() |
c8121176b3 | ||
![]() |
ee270f9b00 | ||
![]() |
bf1d77a4d8 | ||
![]() |
a9344fafe9 | ||
![]() |
b8890726f2 | ||
![]() |
0f84332654 | ||
![]() |
46c82259f7 | ||
![]() |
2d03823283 | ||
![]() |
bba144eca5 | ||
![]() |
9af73dad93 | ||
![]() |
f0d66bf6a6 | ||
![]() |
5ad53a7554 | ||
![]() |
7b2e3331f2 | ||
![]() |
3cb44f6652 | ||
![]() |
b7fdff46f2 | ||
![]() |
e16109330d | ||
![]() |
72621531e0 | ||
![]() |
0a48146efc | ||
![]() |
0c4bf12bfd | ||
![]() |
b8e0855ef3 | ||
![]() |
6467502b9d | ||
![]() |
15b67f20e5 | ||
![]() |
0825179f00 | ||
![]() |
97211d0aad | ||
![]() |
029c499bfa | ||
![]() |
0ba867ec16 | ||
![]() |
866d147122 | ||
![]() |
32851d1bc7 | ||
![]() |
78257408b4 | ||
![]() |
f447b7615e | ||
![]() |
1f780b7209 | ||
![]() |
04bf8a6b1a | ||
![]() |
c4c64854d4 | ||
![]() |
17562dc90b | ||
![]() |
7b24316734 | ||
![]() |
5fab107fd3 | ||
![]() |
f31920e092 | ||
![]() |
eb111a10e7 | ||
![]() |
80b09360c6 | ||
![]() |
5ccf78855d | ||
![]() |
fd5a3b5880 | ||
![]() |
6120c1360c | ||
![]() |
a8087dc12c | ||
![]() |
070c03dbf7 | ||
![]() |
0a9bec3754 |
.github/workflows
NEWSandroid
doc
meson.buildpython/build
src
CommandLine.cxxCommandLine.hxxListen.cxxLogBackend.cxxMain.cxxMapper.cxxMusicBuffer.cxxMusicBuffer.hxxMusicPipe.cxxMusicPipe.hxxPermission.cxxPlaylistFile.cxxPlaylistFile.hxxRemoteTagCache.cxx
archive
client
command
AllCommands.cxxDatabaseCommands.cxxFileCommands.cxxFingerprintCommands.cxxPartitionCommands.cxxPlaylistCommands.cxxQueueCommands.cxx
config
db
decoder
event
filter
plugins
fs
input
AsyncInputStream.cxxBufferingInputStream.cxxInit.cxxInputStream.cxxInputStream.hxxLastInputStream.cxxThreadInputStream.cxx
cache
plugins
lib
alsa
curl
dbus
ffmpeg
fmt
icu
nfs
pcre
pipewire
smbclient
upnp
xiph
yajl
mixer
neighbor
net
output
pcm
player
playlist
plugins
protocol
queue
song
sticker
storage
tag
ApeLoader.cxxBuilder.cxxId3Load.cxxId3MixRamp.cxxId3MixRamp.hxxMixRampInfo.hxxMixRampParser.cxxMixRampParser.hxxTag.cxxmeson.build
thread
util
AllocatedArray.hxxAllocatedString.hxxLazyRandomEngine.cxxLazyRandomEngine.hxxStringAPI.hxxStringStrip.cxxStringStrip.hxxUTF8.cxxUTF8.hxxUriUtil.cxx
zeroconf
systemd
test
win32
132
.github/workflows/build.yml
vendored
Normal file
132
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'android/**'
|
||||
- 'build/**'
|
||||
- 'doc/**'
|
||||
- 'python/**'
|
||||
- 'subprojects/**'
|
||||
- 'systemd/**'
|
||||
- 'win32/**'
|
||||
branches:
|
||||
- master
|
||||
- actions
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'android/**'
|
||||
- 'build/**'
|
||||
- 'doc/**'
|
||||
- 'python/**'
|
||||
- 'subprojects/**'
|
||||
- 'systemd/**'
|
||||
- 'win32/**'
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CC: 'ccache gcc-10'
|
||||
CXX: 'ccache g++-10'
|
||||
steps:
|
||||
- id: checkout
|
||||
uses: actions/checkout@v2
|
||||
- id: cache-ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: linux
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt install -y --no-install-recommends \
|
||||
g++-10 libfmt-dev libboost-dev \
|
||||
libgtest-dev \
|
||||
libpcre2-dev \
|
||||
libsystemd-dev libdbus-1-dev \
|
||||
libicu-dev \
|
||||
libcurl4-gnutls-dev \
|
||||
libpcre2-dev \
|
||||
libavahi-client-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-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 \
|
||||
libmp3lame-dev libtwolame-dev libshine-dev \
|
||||
libsamplerate0-dev libsoxr-dev \
|
||||
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
|
||||
libzzip-dev \
|
||||
libyajl-dev libexpat-dev \
|
||||
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
|
||||
libpulse-dev libshout3-dev \
|
||||
libsndio-dev \
|
||||
libmpdclient-dev \
|
||||
libnfs-dev \
|
||||
libupnp-dev \
|
||||
libsqlite3-dev \
|
||||
libchromaprint-dev \
|
||||
libgcrypt20-dev
|
||||
|
||||
- name: Full Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output/full
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Mini Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output/mini
|
||||
setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
|
||||
meson-version: 0.56.0
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- id: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- id: cache-ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: macos
|
||||
|
||||
- uses: actions/setup-python@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew install \
|
||||
meson ninja \
|
||||
fmt \
|
||||
boost \
|
||||
googletest \
|
||||
icu4c \
|
||||
ffmpeg \
|
||||
libnfs \
|
||||
yajl \
|
||||
libupnp \
|
||||
libid3tag \
|
||||
chromaprint \
|
||||
libsamplerate \
|
||||
libsoxr \
|
||||
flac \
|
||||
opus \
|
||||
libvorbis \
|
||||
faad2 \
|
||||
wavpack \
|
||||
libmpdclient
|
||||
|
||||
- name: Meson Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||
meson-version: 0.56.0
|
51
NEWS
51
NEWS
@@ -1,3 +1,54 @@
|
||||
ver 0.23.5 (2021/12/01)
|
||||
* protocol
|
||||
- support relative offsets for "searchadd"
|
||||
- fix "searchaddpl" bug (bogus error "Bad position")
|
||||
* database
|
||||
- upnp: fix crash bug
|
||||
* tags
|
||||
- fix MixRamp support
|
||||
* migrate to PCRE2
|
||||
* GCC 12 build fixes
|
||||
|
||||
ver 0.23.4 (2021/11/11)
|
||||
* protocol
|
||||
- add optional position parameter to "searchaddpl"
|
||||
* decoder
|
||||
- ffmpeg: support libavcodec 59
|
||||
* output
|
||||
- alsa: add option "thesycon_dsd_workaround" to work around device bug
|
||||
* fix crash on debug builds if startup fails
|
||||
* systemd
|
||||
- remove "RuntimeDirectory" directive because it caused problems
|
||||
- ignore the "pid_file" setting if started as systemd service
|
||||
* Windows
|
||||
- enable the "openmpt" decoder plugin
|
||||
|
||||
ver 0.23.3 (2021/10/31)
|
||||
* protocol
|
||||
- add optional position parameter to "add" and "playlistadd"
|
||||
- allow range in "playlistdelete"
|
||||
* database
|
||||
- fix scanning files with question mark in the name
|
||||
- inotify: fix use-after-free bug
|
||||
* output
|
||||
- alsa: add option "stop_dsd_silence" to work around DSD DAC noise
|
||||
* macOS: fix libfmt related build failure
|
||||
* systemd: add "RuntimeDirectory" directive
|
||||
|
||||
ver 0.23.2 (2021/10/22)
|
||||
* protocol
|
||||
- fix "albumart" timeout bug
|
||||
* input
|
||||
- nfs: fix playback bug
|
||||
* output
|
||||
- pipewire: send artist and title to PipeWire
|
||||
- pipewire: DSD support
|
||||
* neighbor
|
||||
- mention failed plugin name in error message
|
||||
* player
|
||||
- fix cross-fade regression
|
||||
* fix crash with libfmt versions older than 7
|
||||
|
||||
ver 0.23.1 (2021/10/19)
|
||||
* protocol
|
||||
- use decimal notation instead of scientific notation
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="61"
|
||||
android:versionName="0.23.1">
|
||||
android:versionCode="65"
|
||||
android:versionName="0.23.5">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
|
||||
|
@@ -13,7 +13,7 @@ GENCLASS="$D/classes"
|
||||
GENINCLUDE="$D/include"
|
||||
|
||||
mkdir -p "$GENSRC/$JAVA_PKG_PATH"
|
||||
"$JAVAC" -source 1.6 -target 1.6 -Xlint:-options \
|
||||
"$JAVAC" -source 1.7 -target 1.7 -Xlint:-options \
|
||||
-cp "$CLASSPATH" \
|
||||
-h "$GENINCLUDE" \
|
||||
-d "$GENCLASS" \
|
||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.23.1'
|
||||
version = '0.23.5'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
#release = version + '~git'
|
||||
|
||||
@@ -107,6 +107,7 @@ html_theme = 'classic'
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
html_theme_options = {"sidebarwidth": "300px"}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
@@ -128,23 +128,6 @@ audio_output
|
||||
no audio_output section is specified, then MPD will scan for a usable audio
|
||||
output.
|
||||
|
||||
replaygain <off or album or track or auto>
|
||||
If specified, mpd will adjust the volume of songs played using ReplayGain
|
||||
tags (see https://wiki.hydrogenaud.io/index.php?title=Replaygain).
|
||||
Setting this to "album" will
|
||||
adjust volume using the album's ReplayGain tags, while setting it to "track"
|
||||
will adjust it using the track ReplayGain tags. "auto" uses the track
|
||||
ReplayGain tags if random play is activated otherwise the album ReplayGain
|
||||
tags. Currently only FLAC, Ogg Vorbis, Musepack, and MP3 (through ID3v2
|
||||
ReplayGain tags, not APEv2) are supported.
|
||||
|
||||
replaygain_preamp <-15 to 15>
|
||||
This is the gain (in dB) applied to songs with ReplayGain tags.
|
||||
|
||||
volume_normalization <yes or no>
|
||||
If yes, mpd will normalize the volume of songs as they play. The default is
|
||||
no.
|
||||
|
||||
filesystem_charset <charset>
|
||||
This specifies the character set used for the filesystem. A list of supported
|
||||
character sets can be obtained by running "iconv -l". The default is
|
||||
|
@@ -26,22 +26,25 @@
|
||||
# files over an accepted protocol.
|
||||
#
|
||||
#db_file "~/.mpd/database"
|
||||
#
|
||||
|
||||
# These settings are the locations for the daemon log files for the daemon.
|
||||
# These logs are great for troubleshooting, depending on your log_level
|
||||
# settings.
|
||||
#
|
||||
# The special value "syslog" makes MPD use the local syslog daemon. This
|
||||
# setting defaults to logging to syslog.
|
||||
#
|
||||
#log_file "~/.mpd/log"
|
||||
# If you use systemd, do not configure a log_file. With systemd, MPD
|
||||
# defaults to the systemd journal, which is fine.
|
||||
#
|
||||
#log_file "~/.mpd/log"
|
||||
|
||||
# This setting sets the location of the file which stores the process ID
|
||||
# for use of mpd --kill and some init scripts. This setting is disabled by
|
||||
# default and the pid file will not be stored.
|
||||
#
|
||||
#pid_file "~/.mpd/pid"
|
||||
# If you use systemd, do not configure a pid_file.
|
||||
#
|
||||
#pid_file "~/.mpd/pid"
|
||||
|
||||
# This setting sets the location of the file which contains information about
|
||||
# most variables to get MPD back into the same general shape it was in before
|
||||
# it was brought down. This setting is disabled by default and the server
|
||||
|
@@ -61,6 +61,15 @@ upnp
|
||||
|
||||
Provides access to UPnP media servers.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **interface**
|
||||
- Interface used to discover media servers. Decided by upnp if left unconfigured.
|
||||
|
||||
Storage plugins
|
||||
===============
|
||||
|
||||
@@ -836,6 +845,16 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
|
||||
- If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...).
|
||||
* - **dop yes|no**
|
||||
- If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk.
|
||||
* - **stop_dsd_silence yes|no**
|
||||
- If enabled, silence is played before manually stopping playback
|
||||
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
|
||||
workaround for some DACs which emit noise when stopping DSD
|
||||
playback.
|
||||
* - **thesycon_dsd_workaround yes|no**
|
||||
- If enabled, enables a workaround for a bug in Thesycon USB
|
||||
audio receivers. On these devices, playing DSD512 or PCM
|
||||
causes all subsequent attempts to play other DSD rates to fail,
|
||||
which can be fixed by briefly playing PCM at 44.1 kHz.
|
||||
* - **allowed_formats F1 F2 ...**
|
||||
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
|
||||
|
||||
@@ -1094,6 +1113,8 @@ Connect to a `PipeWire <https://pipewire.org/>`_ server. Requires
|
||||
* - **remote NAME**
|
||||
- The name of the remote to connect to. The default is
|
||||
``pipewire-0``.
|
||||
* - **dsd yes|no**
|
||||
- Enable DSD playback. This requires PipeWire 0.38.
|
||||
|
||||
.. _pulse_plugin:
|
||||
|
||||
|
@@ -551,7 +551,7 @@ Playback options
|
||||
|
||||
.. _command_getvol:
|
||||
|
||||
:command:`getvol`
|
||||
:command:`getvol` [#since_0_23]_
|
||||
|
||||
Read the volume. The result is a ``volume:`` line like in
|
||||
:ref:`status <command_status>`. If there is no mixer, MPD will
|
||||
@@ -689,11 +689,14 @@ Whenever possible, ids should be used.
|
||||
|
||||
.. _command_add:
|
||||
|
||||
:command:`add {URI}`
|
||||
:command:`add {URI} [POSITION]`
|
||||
Adds the file ``URI`` to the playlist
|
||||
(directories add recursively). ``URI``
|
||||
can also be a single file.
|
||||
|
||||
The position parameter is the same as in :ref:`addid
|
||||
<command_addid>`. [#since_0_23_3]_
|
||||
|
||||
Clients that are connected via local socket may add arbitrary
|
||||
local files (URI is an absolute path). Example::
|
||||
|
||||
@@ -711,10 +714,10 @@ Whenever possible, ids should be used.
|
||||
|
||||
If the second parameter is given, then the song is inserted at the
|
||||
specified position. If the parameter starts with ``+`` or ``-``,
|
||||
then it is relative to the current song; e.g. ``+0`` inserts right
|
||||
after the current song and ``-0`` inserts right before the current
|
||||
song (i.e. zero songs between the current song and the newly added
|
||||
song).
|
||||
then it is relative to the current song [#since_0_23]_; e.g. ``+0``
|
||||
inserts right after the current song and ``-0`` inserts right
|
||||
before the current song (i.e. zero songs between the current song
|
||||
and the newly added song).
|
||||
|
||||
.. _command_clear:
|
||||
|
||||
@@ -926,16 +929,19 @@ remote playlists (absolute URI with a supported scheme).
|
||||
inserted into the queue; it can be relative as described in
|
||||
:ref:`addid <command_addid>`. (This requires specifying the range
|
||||
as well; the special value `0:` can be used if the whole playlist
|
||||
shall be loaded at a certain queue position.)
|
||||
shall be loaded at a certain queue position.) [#since_0_23_1]_
|
||||
|
||||
.. _command_playlistadd:
|
||||
|
||||
:command:`playlistadd {NAME} {URI}`
|
||||
:command:`playlistadd {NAME} {URI} [POSITION]`
|
||||
Adds ``URI`` to the playlist
|
||||
`NAME.m3u`.
|
||||
`NAME.m3u` will be created if it does
|
||||
not exist.
|
||||
|
||||
The ``POSITION`` parameter specifies where the songs will be
|
||||
inserted into the playlist. [#since_0_23_3]_
|
||||
|
||||
.. _command_playlistclear:
|
||||
|
||||
:command:`playlistclear {NAME}`
|
||||
@@ -947,6 +953,8 @@ remote playlists (absolute URI with a supported scheme).
|
||||
Deletes ``SONGPOS`` from the
|
||||
playlist `NAME.m3u`.
|
||||
|
||||
The second parameter can be a range. [#since_0_23_3]_
|
||||
|
||||
.. _command_playlistmove:
|
||||
|
||||
:command:`playlistmove {NAME} {FROM} {TO}`
|
||||
@@ -1197,7 +1205,7 @@ The music database
|
||||
|
||||
.. _command_search:
|
||||
|
||||
:command:`search {FILTER} [sort {TYPE}] [window {START:END}]
|
||||
:command:`search {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`). Parameters
|
||||
have the same meaning as for :ref:`find <command_find>`,
|
||||
@@ -1213,11 +1221,13 @@ The music database
|
||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||
|
||||
The ``position`` parameter specifies where the songs will be
|
||||
inserted.
|
||||
inserted. [#since_0_23]_
|
||||
It can be relative to the current song as in :ref:`addid
|
||||
<command_addid>`. [#since_0_23_5]_
|
||||
|
||||
.. _command_searchaddpl:
|
||||
|
||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
||||
the playlist named ``NAME``.
|
||||
@@ -1226,6 +1236,9 @@ The music database
|
||||
|
||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||
|
||||
The ``position`` parameter specifies where the songs will be
|
||||
inserted. [#since_0_23_4]_
|
||||
|
||||
.. _command_update:
|
||||
|
||||
:command:`update [URI]`
|
||||
@@ -1644,3 +1657,8 @@ client-to-client messages are local to the current partition.
|
||||
.. [#since_0_20] Since :program:`MPD` 0.20
|
||||
.. [#since_0_21] Since :program:`MPD` 0.21
|
||||
.. [#since_0_22_4] Since :program:`MPD` 0.22.4
|
||||
.. [#since_0_23] Since :program:`MPD` 0.23
|
||||
.. [#since_0_23_1] Since :program:`MPD` 0.23.1
|
||||
.. [#since_0_23_3] Since :program:`MPD` 0.23.3
|
||||
.. [#since_0_23_4] Since :program:`MPD` 0.23.4
|
||||
.. [#since_0_23_5] Since :program:`MPD` 0.23.5
|
||||
|
59
doc/user.rst
59
doc/user.rst
@@ -64,13 +64,13 @@ 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 Buster:
|
||||
For example, the following installs a fairly complete list of build dependencies on Debian Bullseye:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
apt install meson g++ \
|
||||
libfmt-dev \
|
||||
libpcre3-dev \
|
||||
libpcre2-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
@@ -172,7 +172,9 @@ tarball and change into the directory. Then, instead of
|
||||
|
||||
mkdir -p output/win64
|
||||
cd output/win64
|
||||
../../win32/build.py --64
|
||||
../../win32/build.py --64 \
|
||||
--buildtype=debugoptimized -Db_ndebug=true \
|
||||
-Dwrap_mode=forcefallback
|
||||
|
||||
This downloads various library sources, and then configures and builds
|
||||
:program:`MPD` (for x64; to build a 32 bit binary, pass
|
||||
@@ -182,6 +184,11 @@ around. It is large, but easy to use. If you wish to have a small
|
||||
mpd.exe with DLLs, you need to compile manually, without the
|
||||
:file:`build.py` script.
|
||||
|
||||
The option ``-Dwrap_mode=forcefallback`` tells Meson to download and
|
||||
cross-compile several libraries used by MPD instead of looking for
|
||||
them on your computer.
|
||||
|
||||
|
||||
Compiling for Android
|
||||
---------------------
|
||||
|
||||
@@ -205,8 +212,10 @@ tarball and change into the directory. Then, instead of
|
||||
|
||||
mkdir -p output/android
|
||||
cd output/android
|
||||
../../android/build.py SDK_PATH NDK_PATH ABI
|
||||
meson configure -Dandroid_debug_keystore=$HOME/.android/debug.keystore
|
||||
../../android/build.py SDK_PATH NDK_PATH ABI \
|
||||
--buildtype=debugoptimized -Db_ndebug=true \
|
||||
-Dwrap_mode=forcefallback \
|
||||
-Dandroid_debug_keystore=$HOME/.android/debug.keystore
|
||||
ninja android/apk/mpd-debug.apk
|
||||
|
||||
:envvar:`SDK_PATH` is the absolute path where you installed the
|
||||
@@ -456,6 +465,11 @@ The following table lists the audio_output options valid for all plugins:
|
||||
implement an external mixer, see :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.
|
||||
* - **replay_gain_handler software|mixer|none**
|
||||
- Specifies how :ref:`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.
|
||||
* - **filters "name,...**"
|
||||
- The specified configured filters are instantiated in the given
|
||||
order. Each filter name refers to a ``filter`` block, see
|
||||
@@ -574,6 +588,40 @@ Sometimes, music needs to be resampled before it can be played; for example, CDs
|
||||
Check the :ref:`resampler_plugins` reference for a list of resamplers
|
||||
and how to configure them.
|
||||
|
||||
Volume Normalization Settings
|
||||
-----------------------------
|
||||
|
||||
.. _replay_gain:
|
||||
|
||||
Replay Gain
|
||||
^^^^^^^^^^^
|
||||
|
||||
The setting ``replaygain`` specifies whether MPD shall adjust the
|
||||
volume of songs played using `ReplayGain
|
||||
<https://wiki.hydrogenaud.io/index.php?title=Replaygain>`__ tags.
|
||||
Setting this to ``album`` will adjust volume using the album's
|
||||
ReplayGain tags, while setting it to ``track`` will adjust it using
|
||||
the "track" ReplayGain tags. ``auto`` uses the track ReplayGain tags
|
||||
if random play is activated otherwise the album ReplayGain
|
||||
tags.
|
||||
|
||||
If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
|
||||
set to a value (in dB) between ``-15`` and ``15``. This is the gain
|
||||
applied to songs with ReplayGain tags.
|
||||
|
||||
ReplayGain is usually implemented with a software volume filter (which
|
||||
prevents `Bit-perfect playback`_). To use a hardware mixer, set
|
||||
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
|
||||
(see :ref:`config_audio_output` for details).
|
||||
|
||||
Simple Volume Normalization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
MPD implements a very simple volume normalization method which can be
|
||||
enabled by setting ``volume_normalization`` to ``yes``. It supports
|
||||
16 bit PCM only.
|
||||
|
||||
|
||||
Client Connections
|
||||
------------------
|
||||
|
||||
@@ -1067,6 +1115,7 @@ Check list for bit-perfect playback:
|
||||
* Disable sound processing inside ALSA by configuring a "hardware"
|
||||
device (:samp:`hw:0,0` or similar).
|
||||
* Don't use software volume (setting :code:`mixer_type`).
|
||||
* Don't use :ref:`replay_gain`.
|
||||
* Don't force :program:`MPD` to use a specific audio format (settings
|
||||
: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.
|
||||
|
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.23.1',
|
||||
version: '0.23.5',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@@ -44,7 +44,7 @@ version_conf = configuration_data()
|
||||
version_conf.set_quoted('PACKAGE', meson.project_name())
|
||||
version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
version_conf.set_quoted('VERSION', meson.project_version())
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.1')
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.5')
|
||||
configure_file(output: 'Version.h', configuration: version_conf)
|
||||
|
||||
conf = configuration_data()
|
||||
@@ -265,7 +265,6 @@ sources = [
|
||||
version_cxx,
|
||||
'src/Main.cxx',
|
||||
'src/protocol/ArgParser.cxx',
|
||||
'src/protocol/Result.cxx',
|
||||
'src/command/CommandError.cxx',
|
||||
'src/command/PositionArg.cxx',
|
||||
'src/command/AllCommands.cxx',
|
||||
|
@@ -112,12 +112,16 @@ libmodplug = AutotoolsProject(
|
||||
)
|
||||
|
||||
libopenmpt = AutotoolsProject(
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.8+release.autotools.tar.gz',
|
||||
'61de7cc0c011b10472ca16adcc123689',
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
|
||||
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
|
||||
'lib/libopenmpt.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static'
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-openmpt123',
|
||||
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
|
||||
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
|
||||
],
|
||||
base='libopenmpt-0.5.12+release.autotools',
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
@@ -147,8 +151,8 @@ gme = CmakeProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.4.tar.xz',
|
||||
'06b10a183ce5371f915c6bb15b7b1fffbe046e8275099c96affc29e17645d909',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.4.1.tar.xz',
|
||||
'eadbad9e9ab30b25f5520fbfde99fae4a92a1ae3c0257a8d68569a4651e30e02',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
@@ -86,6 +86,9 @@ enum Option {
|
||||
OPTION_KILL,
|
||||
OPTION_NO_CONFIG,
|
||||
OPTION_NO_DAEMON,
|
||||
#ifdef __linux__
|
||||
OPTION_SYSTEMD,
|
||||
#endif
|
||||
OPTION_STDOUT,
|
||||
OPTION_STDERR,
|
||||
OPTION_VERBOSE,
|
||||
@@ -98,6 +101,9 @@ static constexpr OptionDef option_defs[] = {
|
||||
{"kill", "kill the currently running mpd session"},
|
||||
{"no-config", "don't read from config"},
|
||||
{"no-daemon", "don't detach from console"},
|
||||
#ifdef __linux__
|
||||
{"systemd", "systemd service mode"},
|
||||
#endif
|
||||
{"stdout", nullptr}, // hidden, compatibility with old versions
|
||||
{"stderr", "print messages to stderr"},
|
||||
{"verbose", 'v', "verbose logging"},
|
||||
@@ -328,7 +334,7 @@ bool ConfigLoader::TryFile(const AllocatedPath &base_path, Path path)
|
||||
}
|
||||
|
||||
void
|
||||
ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
ConfigData &config)
|
||||
{
|
||||
bool use_config_file = true;
|
||||
@@ -349,6 +355,13 @@ ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
options.daemon = false;
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case OPTION_SYSTEMD:
|
||||
options.daemon = false;
|
||||
options.systemd = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case OPTION_STDOUT:
|
||||
case OPTION_STDERR:
|
||||
options.log_stderr = true;
|
||||
|
@@ -22,15 +22,20 @@
|
||||
|
||||
struct ConfigData;
|
||||
|
||||
struct options {
|
||||
struct CommandLineOptions {
|
||||
bool kill = false;
|
||||
bool daemon = true;
|
||||
|
||||
#ifdef __linux__
|
||||
bool systemd = false;
|
||||
#endif
|
||||
|
||||
bool log_stderr = false;
|
||||
bool verbose = false;
|
||||
};
|
||||
|
||||
void
|
||||
ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
ConfigData &config);
|
||||
|
||||
#endif
|
||||
|
@@ -32,6 +32,7 @@
|
||||
#include "net/SocketUtil.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "fs/XDG.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@@ -85,13 +86,10 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept
|
||||
use $XDG_RUNTIME_DIR */
|
||||
return false;
|
||||
|
||||
Path xdg_runtime_dir = Path::FromFS(getenv("XDG_RUNTIME_DIR"));
|
||||
if (xdg_runtime_dir.IsNull())
|
||||
const auto mpd_runtime_dir = GetAppRuntimeDir();
|
||||
if (mpd_runtime_dir.IsNull())
|
||||
return false;
|
||||
|
||||
const auto mpd_runtime_dir = xdg_runtime_dir / Path::FromFS("mpd");
|
||||
mkdir(mpd_runtime_dir.c_str(), 0700);
|
||||
|
||||
const auto socket_path = mpd_runtime_dir / Path::FromFS("socket");
|
||||
unlink(socket_path.c_str());
|
||||
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "LogBackend.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "Version.h"
|
||||
@@ -110,7 +111,7 @@ chomp_length(std::string_view p) noexcept
|
||||
|
||||
#ifdef HAVE_SYSLOG
|
||||
|
||||
gcc_const
|
||||
[[gnu::const]]
|
||||
static int
|
||||
ToSysLogLevel(LogLevel log_level) noexcept
|
||||
{
|
||||
|
25
src/Main.cxx
25
src/Main.cxx
@@ -142,14 +142,24 @@ struct Config {
|
||||
#ifdef ENABLE_DAEMON
|
||||
|
||||
static void
|
||||
glue_daemonize_init(const struct options *options,
|
||||
glue_daemonize_init(const CommandLineOptions &options,
|
||||
const ConfigData &config)
|
||||
{
|
||||
auto pid_file = config.GetPath(ConfigOption::PID_FILE);
|
||||
|
||||
#ifdef __linux__
|
||||
if (options.systemd && pid_file != nullptr) {
|
||||
pid_file = nullptr;
|
||||
fprintf(stderr,
|
||||
"Ignoring the 'pid_file' setting in systemd mode\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
daemonize_init(config.GetString(ConfigOption::USER),
|
||||
config.GetString(ConfigOption::GROUP),
|
||||
config.GetPath(ConfigOption::PID_FILE));
|
||||
std::move(pid_file));
|
||||
|
||||
if (options->kill)
|
||||
if (options.kill)
|
||||
daemonize_kill();
|
||||
}
|
||||
|
||||
@@ -361,7 +371,8 @@ Instance::BeginShutdownPartitions() noexcept
|
||||
}
|
||||
|
||||
static inline void
|
||||
MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
MainConfigured(const CommandLineOptions &options,
|
||||
const ConfigData &raw_config)
|
||||
{
|
||||
#ifdef ENABLE_DAEMON
|
||||
daemonize_close_stdin();
|
||||
@@ -384,7 +395,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
const Config config(raw_config);
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
glue_daemonize_init(&options, raw_config);
|
||||
glue_daemonize_init(options, raw_config);
|
||||
#endif
|
||||
|
||||
TagLoadConfig(raw_config);
|
||||
@@ -582,7 +593,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
static void
|
||||
AndroidMain()
|
||||
{
|
||||
struct options options;
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
||||
@@ -642,7 +653,7 @@ Java_org_musicpd_Bridge_pause(JNIEnv *, jclass)
|
||||
static inline void
|
||||
MainOrThrow(int argc, char *argv[])
|
||||
{
|
||||
struct options options;
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
ParseCommandLine(argc, argv, options, raw_config);
|
||||
|
@@ -85,15 +85,15 @@ map_fs_to_utf8(Path path_fs) noexcept
|
||||
{
|
||||
if (path_fs.IsAbsolute()) {
|
||||
if (global_instance->storage == nullptr)
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
const auto music_dir_fs = global_instance->storage->MapFS("");
|
||||
if (music_dir_fs.IsNull())
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
auto relative = music_dir_fs.Relative(path_fs);
|
||||
if (relative == nullptr || StringIsEmpty(relative))
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
path_fs = Path::FromFS(relative);
|
||||
}
|
||||
|
@@ -29,8 +29,8 @@ MusicBuffer::MusicBuffer(unsigned num_chunks)
|
||||
MusicChunkPtr
|
||||
MusicBuffer::Allocate() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return MusicChunkPtr(buffer.Allocate(), MusicChunkDeleter(*this));
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return {buffer.Allocate(), MusicChunkDeleter(*this)};
|
||||
}
|
||||
|
||||
void
|
||||
@@ -44,7 +44,7 @@ MusicBuffer::Return(MusicChunk *chunk) noexcept
|
||||
chunk->next.reset();
|
||||
chunk->other.reset();
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
assert(!chunk->other || !chunk->other->other);
|
||||
|
||||
|
@@ -54,7 +54,7 @@ public:
|
||||
#endif
|
||||
|
||||
bool IsFull() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return buffer.IsFull();
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,7 @@
|
||||
bool
|
||||
MusicPipe::Contains(const MusicChunk *chunk) const noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
for (const MusicChunk *i = head.get(); i != nullptr; i = i->next.get())
|
||||
if (i == chunk)
|
||||
@@ -41,7 +41,7 @@ MusicPipe::Contains(const MusicChunk *chunk) const noexcept
|
||||
MusicChunkPtr
|
||||
MusicPipe::Shift() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
auto chunk = std::move(head);
|
||||
if (chunk != nullptr) {
|
||||
@@ -81,7 +81,7 @@ MusicPipe::Push(MusicChunkPtr chunk) noexcept
|
||||
assert(!chunk->IsEmpty());
|
||||
assert(chunk->length == 0 || chunk->audio_format.IsValid());
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
assert(size > 0 || !audio_format.IsDefined());
|
||||
assert(!audio_format.IsDefined() ||
|
||||
|
@@ -77,7 +77,7 @@ public:
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
const MusicChunk *Peek() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return head.get();
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public:
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
unsigned GetSize() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@@ -30,7 +30,6 @@
|
||||
#include "util/StringView.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -100,18 +99,15 @@ initPermissions(const ConfigData &config)
|
||||
for (const auto ¶m : config.GetParamList(ConfigOption::PASSWORD)) {
|
||||
permission_default = 0;
|
||||
|
||||
param.With([](const char *value){
|
||||
const char *separator = std::strchr(value,
|
||||
PERMISSION_PASSWORD_CHAR);
|
||||
|
||||
if (separator == nullptr)
|
||||
param.With([](const StringView value){
|
||||
const auto [password, permissions] =
|
||||
value.Split(PERMISSION_PASSWORD_CHAR);
|
||||
if (permissions == nullptr)
|
||||
throw FormatRuntimeError("\"%c\" not found in password string",
|
||||
PERMISSION_PASSWORD_CHAR);
|
||||
|
||||
std::string password(value, separator);
|
||||
|
||||
unsigned permission = parsePermissions(separator + 1);
|
||||
permission_passwords.emplace(std::move(password), permission);
|
||||
permission_passwords.emplace(password,
|
||||
parsePermissions(permissions));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "Mapper.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/FileOutputStream.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
@@ -34,7 +35,6 @@
|
||||
#include "config/Defaults.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "fs/Limits.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/FileInfo.hxx"
|
||||
@@ -173,11 +173,8 @@ ListPlaylistFiles()
|
||||
}
|
||||
|
||||
static void
|
||||
SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path)
|
||||
SavePlaylistFile(Path path_fs, const PlaylistFileContents &contents)
|
||||
{
|
||||
assert(utf8path != nullptr);
|
||||
|
||||
const auto path_fs = spl_map_to_fs(utf8path);
|
||||
assert(!path_fs.IsNull());
|
||||
|
||||
FileOutputStream fos(path_fs);
|
||||
@@ -191,12 +188,11 @@ SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path)
|
||||
fos.Commit();
|
||||
}
|
||||
|
||||
PlaylistFileContents
|
||||
LoadPlaylistFile(const char *utf8path)
|
||||
static PlaylistFileContents
|
||||
LoadPlaylistFile(Path path_fs)
|
||||
try {
|
||||
PlaylistFileContents contents;
|
||||
|
||||
const auto path_fs = spl_map_to_fs(utf8path);
|
||||
assert(!path_fs.IsNull());
|
||||
|
||||
TextFile file(path_fs);
|
||||
@@ -251,16 +247,54 @@ try {
|
||||
throw;
|
||||
}
|
||||
|
||||
void
|
||||
spl_move_index(const char *utf8path, unsigned src, unsigned dest)
|
||||
static PlaylistFileContents
|
||||
MaybeLoadPlaylistFile(Path path_fs, PlaylistFileEditor::LoadMode load_mode)
|
||||
try {
|
||||
if (load_mode == PlaylistFileEditor::LoadMode::NO)
|
||||
return {};
|
||||
|
||||
return LoadPlaylistFile(path_fs);
|
||||
} catch (const PlaylistError &error) {
|
||||
if (error.GetCode() == PlaylistResult::NO_SUCH_LIST &&
|
||||
load_mode == PlaylistFileEditor::LoadMode::TRY)
|
||||
return {};
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
PlaylistFileEditor::PlaylistFileEditor(const char *name_utf8,
|
||||
LoadMode load_mode)
|
||||
:path(spl_map_to_fs(name_utf8)),
|
||||
contents(MaybeLoadPlaylistFile(path, load_mode))
|
||||
{
|
||||
if (src == dest)
|
||||
/* this doesn't check whether the playlist exists, but
|
||||
what the hell.. */
|
||||
return;
|
||||
}
|
||||
|
||||
auto contents = LoadPlaylistFile(utf8path);
|
||||
void
|
||||
PlaylistFileEditor::Insert(std::size_t i, const char *uri)
|
||||
{
|
||||
if (i > size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad position");
|
||||
|
||||
if (size() >= playlist_max_length)
|
||||
throw PlaylistError(PlaylistResult::TOO_LARGE,
|
||||
"Stored playlist is too large");
|
||||
|
||||
contents.emplace(std::next(contents.begin(), i), uri);
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::Insert(std::size_t i, const DetachedSong &song)
|
||||
{
|
||||
const char *uri = playlist_saveAbsolutePaths
|
||||
? song.GetRealURI()
|
||||
: song.GetURI();
|
||||
|
||||
Insert(i, uri);
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::MoveIndex(unsigned src, unsigned dest)
|
||||
{
|
||||
if (src >= contents.size() || dest >= contents.size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||
|
||||
@@ -270,9 +304,31 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest)
|
||||
|
||||
const auto dest_i = std::next(contents.begin(), dest);
|
||||
contents.insert(dest_i, std::move(value));
|
||||
}
|
||||
|
||||
SavePlaylistFile(contents, utf8path);
|
||||
void
|
||||
PlaylistFileEditor::RemoveIndex(unsigned i)
|
||||
{
|
||||
if (i >= contents.size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||
|
||||
contents.erase(std::next(contents.begin(), i));
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::RemoveRange(RangeArg range)
|
||||
{
|
||||
if (!range.CheckClip(size()))
|
||||
throw PlaylistError::BadRange();
|
||||
|
||||
contents.erase(std::next(contents.begin(), range.start),
|
||||
std::next(contents.begin(), range.end));
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::Save()
|
||||
{
|
||||
SavePlaylistFile(path, contents);
|
||||
idle_add(IDLE_STORED_PLAYLIST);
|
||||
}
|
||||
|
||||
@@ -314,20 +370,6 @@ spl_delete(const char *name_utf8)
|
||||
idle_add(IDLE_STORED_PLAYLIST);
|
||||
}
|
||||
|
||||
void
|
||||
spl_remove_index(const char *utf8path, unsigned pos)
|
||||
{
|
||||
auto contents = LoadPlaylistFile(utf8path);
|
||||
|
||||
if (pos >= contents.size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||
|
||||
contents.erase(std::next(contents.begin(), pos));
|
||||
|
||||
SavePlaylistFile(contents, utf8path);
|
||||
idle_add(IDLE_STORED_PLAYLIST);
|
||||
}
|
||||
|
||||
void
|
||||
spl_append_song(const char *utf8path, const DetachedSong &song)
|
||||
try {
|
||||
|
@@ -20,19 +20,55 @@
|
||||
#ifndef MPD_PLAYLIST_FILE_HXX
|
||||
#define MPD_PLAYLIST_FILE_HXX
|
||||
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
struct ConfigData;
|
||||
struct RangeArg;
|
||||
class DetachedSong;
|
||||
class SongLoader;
|
||||
class PlaylistVector;
|
||||
class AllocatedPath;
|
||||
|
||||
typedef std::vector<std::string> PlaylistFileContents;
|
||||
|
||||
extern bool playlist_saveAbsolutePaths;
|
||||
|
||||
class PlaylistFileEditor {
|
||||
const AllocatedPath path;
|
||||
|
||||
PlaylistFileContents contents;
|
||||
|
||||
public:
|
||||
enum class LoadMode {
|
||||
NO,
|
||||
YES,
|
||||
TRY,
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws on error.
|
||||
*/
|
||||
explicit PlaylistFileEditor(const char *name_utf8, LoadMode load_mode);
|
||||
|
||||
auto size() const noexcept {
|
||||
return contents.size();
|
||||
}
|
||||
|
||||
void Insert(std::size_t i, const char *uri);
|
||||
void Insert(std::size_t i, const DetachedSong &song);
|
||||
|
||||
void MoveIndex(unsigned src, unsigned dest);
|
||||
void RemoveIndex(unsigned i);
|
||||
void RemoveRange(RangeArg range);
|
||||
|
||||
void Save();
|
||||
|
||||
private:
|
||||
void Load();
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform some global initialization, e.g. load configuration values.
|
||||
*/
|
||||
@@ -55,21 +91,12 @@ spl_map_to_fs(const char *name_utf8);
|
||||
PlaylistVector
|
||||
ListPlaylistFiles();
|
||||
|
||||
PlaylistFileContents
|
||||
LoadPlaylistFile(const char *utf8path);
|
||||
|
||||
void
|
||||
spl_move_index(const char *utf8path, unsigned src, unsigned dest);
|
||||
|
||||
void
|
||||
spl_clear(const char *utf8path);
|
||||
|
||||
void
|
||||
spl_delete(const char *name_utf8);
|
||||
|
||||
void
|
||||
spl_remove_index(const char *utf8path, unsigned pos);
|
||||
|
||||
void
|
||||
spl_append_song(const char *utf8path, const DetachedSong &song);
|
||||
|
||||
|
@@ -98,7 +98,7 @@ RemoteTagCache::ItemResolved(Item &item) noexcept
|
||||
void
|
||||
RemoteTagCache::InvokeHandlers() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
|
||||
while (!invoke_list.empty()) {
|
||||
auto &item = invoke_list.front();
|
||||
@@ -125,7 +125,7 @@ RemoteTagCache::Item::OnRemoteTag(Tag &&_tag) noexcept
|
||||
|
||||
scanner.reset();
|
||||
|
||||
const std::lock_guard<Mutex> lock(parent.mutex);
|
||||
const std::scoped_lock<Mutex> lock(parent.mutex);
|
||||
parent.ItemResolved(*this);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,6 @@ RemoteTagCache::Item::OnRemoteTagError(std::exception_ptr e) noexcept
|
||||
|
||||
scanner.reset();
|
||||
|
||||
const std::lock_guard<Mutex> lock(parent.mutex);
|
||||
const std::scoped_lock<Mutex> lock(parent.mutex);
|
||||
parent.ItemResolved(*this);
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "plugins/ZzipArchivePlugin.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
@@ -162,7 +162,7 @@ class Iso9660InputStream final : public InputStream {
|
||||
std::array<uint8_t, ISO_BLOCKSIZE> data;
|
||||
|
||||
public:
|
||||
ConstBuffer<uint8_t> Read() const noexcept {
|
||||
[[nodiscard]] ConstBuffer<uint8_t> Read() const noexcept {
|
||||
assert(fill <= data.size());
|
||||
assert(position <= fill);
|
||||
|
||||
|
@@ -35,7 +35,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <inttypes.h> /* for PRIoffset (PRIu64) */
|
||||
#include <cinttypes> /* for PRIoffset (PRIu64) */
|
||||
|
||||
struct ZzipDir {
|
||||
ZZIP_DIR *const dir;
|
||||
|
@@ -150,7 +150,13 @@ public:
|
||||
/**
|
||||
* Write a null-terminated string.
|
||||
*/
|
||||
bool Write(const char *data) noexcept;
|
||||
bool Write(std::string_view s) noexcept {
|
||||
return Write(s.data(), s.size());
|
||||
}
|
||||
|
||||
bool WriteOK() noexcept {
|
||||
return Write("OK\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the uid of the client process, or a negative value
|
||||
|
@@ -20,7 +20,6 @@
|
||||
#include "Client.hxx"
|
||||
#include "Config.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "protocol/Result.hxx"
|
||||
#include "command/AllCommands.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
@@ -72,7 +71,7 @@ Client::ProcessLine(char *line) noexcept
|
||||
if (idle_waiting) {
|
||||
/* send empty idle response and leave idle mode */
|
||||
idle_waiting = false;
|
||||
command_success(*this);
|
||||
WriteOK();
|
||||
}
|
||||
|
||||
/* do nothing if the client wasn't idling: the client
|
||||
@@ -108,7 +107,7 @@ Client::ProcessLine(char *line) noexcept
|
||||
"list returned {}", id, unsigned(ret));
|
||||
|
||||
if (ret == CommandResult::OK)
|
||||
command_success(*this);
|
||||
WriteOK();
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
@@ -144,7 +143,7 @@ Client::ProcessLine(char *line) noexcept
|
||||
return CommandResult::CLOSE;
|
||||
|
||||
if (ret == CommandResult::OK)
|
||||
command_success(*this);
|
||||
WriteOK();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@@ -66,7 +66,11 @@ Response::WriteBinary(ConstBuffer<void> payload) noexcept
|
||||
void
|
||||
Response::Error(enum ack code, const char *msg) noexcept
|
||||
{
|
||||
FmtError(code, FMT_STRING("{}"), msg);
|
||||
Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
|
||||
(int)code, list_index, command);
|
||||
|
||||
Write(msg);
|
||||
Write("\n");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -76,7 +80,7 @@ Response::VFmtError(enum ack code,
|
||||
Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
|
||||
(int)code, list_index, command);
|
||||
|
||||
VFmt(format_str, std::move(args));
|
||||
VFmt(format_str, args);
|
||||
|
||||
Write("\n");
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@
|
||||
#include "Client.hxx"
|
||||
#include "Response.hxx"
|
||||
#include "command/CommandError.hxx"
|
||||
#include "protocol/Result.hxx"
|
||||
|
||||
ThreadBackgroundCommand::ThreadBackgroundCommand(Client &_client) noexcept
|
||||
:thread(BIND_THIS_METHOD(_Run)),
|
||||
@@ -57,7 +56,7 @@ ThreadBackgroundCommand::DeferredFinish() noexcept
|
||||
PrintError(response, error);
|
||||
} else {
|
||||
SendResponse(response);
|
||||
command_success(client);
|
||||
client.WriteOK();
|
||||
}
|
||||
|
||||
/* delete this object */
|
||||
|
@@ -27,9 +27,3 @@ Client::Write(const void *data, size_t length) noexcept
|
||||
/* if the client is going to be closed, do nothing */
|
||||
return !IsExpired() && FullyBufferedSocket::Write(data, length);
|
||||
}
|
||||
|
||||
bool
|
||||
Client::Write(const char *data) noexcept
|
||||
{
|
||||
return Write(data, strlen(data));
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ handle_not_commands(Client &client, Request request, Response &response);
|
||||
* This array must be sorted!
|
||||
*/
|
||||
static constexpr struct command commands[] = {
|
||||
{ "add", PERMISSION_ADD, 1, 1, handle_add },
|
||||
{ "add", PERMISSION_ADD, 1, 2, handle_add },
|
||||
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
|
||||
{ "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
|
||||
{ "albumart", PERMISSION_READ, 2, 2, handle_album_art },
|
||||
@@ -157,7 +157,7 @@ static constexpr struct command commands[] = {
|
||||
{ "play", PERMISSION_PLAYER, 0, 1, handle_play },
|
||||
{ "playid", PERMISSION_PLAYER, 0, 1, handle_playid },
|
||||
{ "playlist", PERMISSION_READ, 0, 0, handle_playlist },
|
||||
{ "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd },
|
||||
{ "playlistadd", PERMISSION_CONTROL, 2, 3, handle_playlistadd },
|
||||
{ "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
|
||||
{ "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete },
|
||||
{ "playlistfind", PERMISSION_READ, 1, -1, handle_playlistfind },
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "DatabaseCommands.hxx"
|
||||
#include "PositionArg.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "db/DatabaseQueue.hxx"
|
||||
@@ -86,6 +87,20 @@ ParseQueuePosition(Request &args, unsigned queue_length)
|
||||
return queue_length;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
ParseInsertPosition(Request &args, const playlist &playlist)
|
||||
{
|
||||
if (args.size >= 2 && StringIsEqual(args[args.size - 2], "position")) {
|
||||
unsigned position = ParseInsertPosition(args.back(), playlist);
|
||||
args.pop_back();
|
||||
args.pop_back();
|
||||
return position;
|
||||
}
|
||||
|
||||
/* append to the end of the queue by default */
|
||||
return playlist.queue.GetLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all remaining arguments to a #DatabaseSelection.
|
||||
*
|
||||
@@ -160,7 +175,8 @@ handle_match_add(Client &client, Request args, bool fold_case)
|
||||
{
|
||||
auto &partition = client.GetPartition();
|
||||
const auto queue_length = partition.playlist.queue.GetLength();
|
||||
const unsigned position = ParseQueuePosition(args, queue_length);
|
||||
const unsigned position =
|
||||
ParseInsertPosition(args, partition.playlist);
|
||||
|
||||
SongFilter filter;
|
||||
const auto selection = ParseDatabaseSelection(args, fold_case, filter);
|
||||
@@ -199,13 +215,20 @@ handle_searchaddpl(Client &client, Request args, Response &)
|
||||
{
|
||||
const char *playlist = args.shift();
|
||||
|
||||
const unsigned position = ParseQueuePosition(args, UINT_MAX);
|
||||
|
||||
SongFilter filter;
|
||||
const auto selection = ParseDatabaseSelection(args, true, filter);
|
||||
|
||||
const Database &db = client.GetDatabaseOrThrow();
|
||||
|
||||
search_add_to_playlist(db, client.GetStorage(),
|
||||
playlist, selection);
|
||||
if (position == UINT_MAX)
|
||||
search_add_to_playlist(db, client.GetStorage(),
|
||||
playlist, selection);
|
||||
else
|
||||
SearchInsertIntoPlaylist(db, client.GetStorage(), selection,
|
||||
playlist, position);
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
|
@@ -213,7 +213,7 @@ read_stream_art(Response &r, const std::string_view art_directory,
|
||||
std::min<offset_type>(art_file_size - offset,
|
||||
r.GetClient().binary_limit);
|
||||
|
||||
std::unique_ptr<std::byte[]> buffer(new std::byte[buffer_size]);
|
||||
auto buffer = std::make_unique<std::byte[]>(buffer_size);
|
||||
|
||||
std::size_t read_size = 0;
|
||||
if (buffer_size > 0) {
|
||||
|
@@ -67,7 +67,7 @@ protected:
|
||||
}
|
||||
|
||||
void CancelThread() noexcept override {
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
cancel = true;
|
||||
cond.notify_one();
|
||||
}
|
||||
@@ -204,7 +204,7 @@ GetChromaprintCommand::DecodeFile(std::string_view suffix, InputStream &is,
|
||||
return false;
|
||||
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
if (cancel)
|
||||
throw StopDecoder();
|
||||
}
|
||||
@@ -224,10 +224,12 @@ GetChromaprintCommand::DecodeFile(std::string_view suffix, InputStream &is,
|
||||
inline void
|
||||
GetChromaprintCommand::DecodeFile()
|
||||
{
|
||||
const auto suffix = uri_get_suffix(uri);
|
||||
if (suffix.empty())
|
||||
const char *_suffix = PathTraitsUTF8::GetFilenameSuffix(uri.c_str());
|
||||
if (_suffix == nullptr)
|
||||
return;
|
||||
|
||||
const std::string_view suffix{_suffix};
|
||||
|
||||
InputStreamPtr input_stream;
|
||||
|
||||
try {
|
||||
|
@@ -186,7 +186,8 @@ handle_moveoutput(Client &client, Request request, Response &response)
|
||||
was_enabled);
|
||||
else
|
||||
/* copy the AudioOutputControl and add it to the output list */
|
||||
dest_partition.outputs.AddCopy(output,was_enabled);
|
||||
dest_partition.outputs.AddMoveFrom(std::move(*output),
|
||||
was_enabled);
|
||||
|
||||
instance.EmitIdle(IDLE_OUTPUT);
|
||||
return CommandResult::OK;
|
||||
|
@@ -22,8 +22,10 @@
|
||||
#include "PositionArg.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "db/Interface.hxx"
|
||||
#include "db/Selection.hxx"
|
||||
#include "db/DatabasePlaylist.hxx"
|
||||
#include "db/DatabaseSong.hxx"
|
||||
#include "PlaylistSave.hxx"
|
||||
#include "PlaylistFile.hxx"
|
||||
#include "PlaylistError.hxx"
|
||||
@@ -173,9 +175,11 @@ handle_playlistdelete([[maybe_unused]] Client &client,
|
||||
Request args, [[maybe_unused]] Response &r)
|
||||
{
|
||||
const char *const name = args[0];
|
||||
unsigned from = args.ParseUnsigned(1);
|
||||
const auto range = args.ParseRange(1);
|
||||
|
||||
spl_remove_index(name, from);
|
||||
PlaylistFileEditor editor(name, PlaylistFileEditor::LoadMode::YES);
|
||||
editor.RemoveRange(range);
|
||||
editor.Save();
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
@@ -187,7 +191,14 @@ handle_playlistmove([[maybe_unused]] Client &client,
|
||||
unsigned from = args.ParseUnsigned(1);
|
||||
unsigned to = args.ParseUnsigned(2);
|
||||
|
||||
spl_move_index(name, from, to);
|
||||
if (from == to)
|
||||
/* this doesn't check whether the playlist exists, but
|
||||
what the hell.. */
|
||||
return CommandResult::OK;
|
||||
|
||||
PlaylistFileEditor editor(name, PlaylistFileEditor::LoadMode::YES);
|
||||
editor.MoveIndex(from, to);
|
||||
editor.Save();
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
@@ -201,12 +212,55 @@ handle_playlistclear([[maybe_unused]] Client &client,
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
static CommandResult
|
||||
handle_playlistadd_position(Client &client, const char *playlist_name,
|
||||
const char *uri, unsigned position,
|
||||
Response &r)
|
||||
{
|
||||
PlaylistFileEditor editor{
|
||||
playlist_name,
|
||||
PlaylistFileEditor::LoadMode::TRY,
|
||||
};
|
||||
|
||||
if (position > editor.size()) {
|
||||
r.Error(ACK_ERROR_ARG, "Bad position");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
if (uri_has_scheme(uri)) {
|
||||
editor.Insert(position, uri);
|
||||
} else {
|
||||
#ifdef ENABLE_DATABASE
|
||||
const DatabaseSelection selection(uri, true, nullptr);
|
||||
|
||||
if (SearchInsertIntoPlaylist(client.GetDatabaseOrThrow(),
|
||||
client.GetStorage(),
|
||||
selection,
|
||||
editor, position) == 0)
|
||||
/* no song was found, don't need to save */
|
||||
return CommandResult::OK;
|
||||
#else
|
||||
(void)client;
|
||||
r.Error(ACK_ERROR_NO_EXIST, "No database");
|
||||
return CommandResult::ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
editor.Save();
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
CommandResult
|
||||
handle_playlistadd(Client &client, Request args, [[maybe_unused]] Response &r)
|
||||
{
|
||||
const char *const playlist = args[0];
|
||||
const char *const uri = args[1];
|
||||
|
||||
if (args.size >= 3)
|
||||
return handle_playlistadd_position(client, playlist, uri,
|
||||
args.ParseUnsigned(2), r);
|
||||
|
||||
if (uri_has_scheme(uri)) {
|
||||
const SongLoader loader(client);
|
||||
spl_append_uri(playlist, loader, uri);
|
||||
|
@@ -52,29 +52,24 @@ AddUri(Client &client, const LocatedUri &uri)
|
||||
SongLoader(client).LoadSong(uri));
|
||||
}
|
||||
|
||||
static CommandResult
|
||||
AddDatabaseSelection(Client &client, const char *uri,
|
||||
[[maybe_unused]] Response &r)
|
||||
{
|
||||
#ifdef ENABLE_DATABASE
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
static void
|
||||
AddDatabaseSelection(Partition &partition, const char *uri)
|
||||
{
|
||||
const ScopeBulkEdit bulk_edit(partition);
|
||||
|
||||
const DatabaseSelection selection(uri, true);
|
||||
AddFromDatabase(partition, selection);
|
||||
return CommandResult::OK;
|
||||
#else
|
||||
(void)client;
|
||||
(void)uri;
|
||||
|
||||
r.Error(ACK_ERROR_NO_EXIST, "No database");
|
||||
return CommandResult::ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
CommandResult
|
||||
handle_add(Client &client, Request args, Response &r)
|
||||
handle_add(Client &client, Request args, [[maybe_unused]] Response &r)
|
||||
{
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
const char *uri = args.front();
|
||||
if (StringIsEqual(uri, "/"))
|
||||
/* this URI is malformed, but some clients are buggy
|
||||
@@ -84,6 +79,11 @@ handle_add(Client &client, Request args, Response &r)
|
||||
here */
|
||||
uri = "";
|
||||
|
||||
const auto old_size = partition.playlist.GetLength();
|
||||
const unsigned position = args.size > 1
|
||||
? ParseInsertPosition(args[1], partition.playlist)
|
||||
: old_size;
|
||||
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
@@ -94,18 +94,34 @@ handle_add(Client &client, Request args, Response &r)
|
||||
case LocatedUri::Type::ABSOLUTE:
|
||||
AddUri(client, located_uri);
|
||||
client.GetInstance().LookupRemoteTag(located_uri.canonical_uri);
|
||||
return CommandResult::OK;
|
||||
break;
|
||||
|
||||
case LocatedUri::Type::PATH:
|
||||
AddUri(client, located_uri);
|
||||
return CommandResult::OK;
|
||||
break;
|
||||
|
||||
case LocatedUri::Type::RELATIVE:
|
||||
return AddDatabaseSelection(client, located_uri.canonical_uri,
|
||||
r);
|
||||
#ifdef ENABLE_DATABASE
|
||||
AddDatabaseSelection(partition, located_uri.canonical_uri);
|
||||
break;
|
||||
#else
|
||||
r.Error(ACK_ERROR_NO_EXIST, "No database");
|
||||
return CommandResult::ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
gcc_unreachable();
|
||||
if (position < old_size) {
|
||||
const unsigned new_size = partition.playlist.GetLength();
|
||||
const RangeArg move_range{old_size, new_size};
|
||||
|
||||
try {
|
||||
partition.MoveRange(move_range, position);
|
||||
} catch (...) {
|
||||
/* ignore - shall we handle it? */
|
||||
}
|
||||
}
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
CommandResult
|
||||
|
@@ -23,11 +23,10 @@
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <pwd.h>
|
||||
|
||||
@@ -96,30 +95,18 @@ ParsePath(const char *path)
|
||||
if (*path == '\0')
|
||||
return GetConfiguredHome();
|
||||
|
||||
AllocatedPath home = nullptr;
|
||||
|
||||
if (*path == '/') {
|
||||
home = GetConfiguredHome();
|
||||
|
||||
++path;
|
||||
|
||||
return GetConfiguredHome() /
|
||||
AllocatedPath::FromUTF8Throw(path);
|
||||
} else {
|
||||
const char *slash = std::strchr(path, '/');
|
||||
const char *end = slash == nullptr
|
||||
? path + strlen(path)
|
||||
: slash;
|
||||
const std::string user(path, end);
|
||||
home = GetHome(user.c_str());
|
||||
const auto [user, rest] =
|
||||
StringView{path}.Split('/');
|
||||
|
||||
if (slash == nullptr)
|
||||
return home;
|
||||
|
||||
path = slash + 1;
|
||||
return GetHome(std::string{user}.c_str())
|
||||
/ AllocatedPath::FromUTF8Throw(rest);
|
||||
}
|
||||
|
||||
if (home.IsNull())
|
||||
return nullptr;
|
||||
|
||||
return home / AllocatedPath::FromUTF8Throw(path);
|
||||
} else if (!PathTraitsUTF8::IsAbsolute(path)) {
|
||||
throw FormatRuntimeError("not an absolute path: %s", path);
|
||||
} else {
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "PlaylistFile.hxx"
|
||||
#include "Interface.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -41,3 +42,41 @@ search_add_to_playlist(const Database &db, const Storage *storage,
|
||||
const auto f = [=](auto && arg1) { return AddSong(storage, playlist_path_utf8, arg1); };
|
||||
db.Visit(selection, f);
|
||||
}
|
||||
|
||||
unsigned
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
PlaylistFileEditor &playlist,
|
||||
unsigned position)
|
||||
{
|
||||
assert(position <= playlist.size());
|
||||
|
||||
unsigned n = 0;
|
||||
|
||||
db.Visit(selection, [&playlist, position, &n, storage](const auto &song){
|
||||
playlist.Insert(position + n,
|
||||
DatabaseDetachSong(storage, song));
|
||||
++n;
|
||||
});
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
const char *playlist_name,
|
||||
unsigned position)
|
||||
{
|
||||
PlaylistFileEditor editor{
|
||||
playlist_name,
|
||||
PlaylistFileEditor::LoadMode::TRY,
|
||||
};
|
||||
|
||||
if (position > editor.size())
|
||||
throw ProtocolError{ACK_ERROR_ARG, "Bad position"};
|
||||
|
||||
if (SearchInsertIntoPlaylist(db, storage, selection,
|
||||
editor, position) > 0)
|
||||
editor.Save();
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
class Database;
|
||||
class Storage;
|
||||
struct DatabaseSelection;
|
||||
class PlaylistFileEditor;
|
||||
|
||||
gcc_nonnull(3)
|
||||
void
|
||||
@@ -32,4 +33,19 @@ search_add_to_playlist(const Database &db, const Storage *storage,
|
||||
const char *playlist_path_utf8,
|
||||
const DatabaseSelection &selection);
|
||||
|
||||
/**
|
||||
* @return the number of songs added
|
||||
*/
|
||||
unsigned
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
PlaylistFileEditor &playlist,
|
||||
unsigned position);
|
||||
|
||||
void
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
const char *playlist_name,
|
||||
unsigned position);
|
||||
|
||||
#endif
|
||||
|
@@ -35,6 +35,7 @@ db_plugins = static_library(
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
upnp_dep,
|
||||
pcre_dep,
|
||||
libmpdclient_dep,
|
||||
log_dep,
|
||||
],
|
||||
|
@@ -278,5 +278,5 @@ Directory::Walk(bool recursive, const SongFilter *filter,
|
||||
LightDirectory
|
||||
Directory::Export() const noexcept
|
||||
{
|
||||
return LightDirectory(GetPath(), mtime);
|
||||
return {GetPath(), mtime};
|
||||
}
|
||||
|
@@ -316,7 +316,7 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
|
||||
|
||||
if (r.rest.find('/') == std::string_view::npos) {
|
||||
if (visit_song) {
|
||||
Song *song = r.directory->FindSong(r.rest);
|
||||
const Song *song = r.directory->FindSong(r.rest);
|
||||
if (song != nullptr) {
|
||||
const auto song2 = song->Export();
|
||||
if (selection.Match(song2))
|
||||
|
@@ -39,6 +39,7 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
#include "util/SplitString.hxx"
|
||||
#include "config/Block.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
@@ -76,10 +77,13 @@ class UpnpDatabase : public Database {
|
||||
UpnpClient_Handle handle;
|
||||
UPnPDeviceDirectory *discovery;
|
||||
|
||||
const char* interface;
|
||||
|
||||
public:
|
||||
explicit UpnpDatabase(EventLoop &_event_loop) noexcept
|
||||
explicit UpnpDatabase(EventLoop &_event_loop, const ConfigBlock &block) noexcept
|
||||
:Database(upnp_db_plugin),
|
||||
event_loop(_event_loop) {}
|
||||
event_loop(_event_loop),
|
||||
interface(block.GetBlockValue("interface", nullptr)) {}
|
||||
|
||||
static DatabasePtr Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
@@ -147,15 +151,15 @@ private:
|
||||
DatabasePtr
|
||||
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
|
||||
[[maybe_unused]] DatabaseListener &listener,
|
||||
const ConfigBlock &) noexcept
|
||||
const ConfigBlock &block) noexcept
|
||||
{
|
||||
return std::make_unique<UpnpDatabase>(io_event_loop);
|
||||
return std::make_unique<UpnpDatabase>(io_event_loop, block);;
|
||||
}
|
||||
|
||||
void
|
||||
UpnpDatabase::Open()
|
||||
{
|
||||
handle = UpnpClientGlobalInit();
|
||||
handle = UpnpClientGlobalInit(interface);
|
||||
|
||||
discovery = new UPnPDeviceDirectory(event_loop, handle);
|
||||
try {
|
||||
@@ -246,11 +250,11 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
|
||||
{
|
||||
const SongFilter *filter = selection.filter;
|
||||
if (selection.filter == nullptr)
|
||||
return UPnPDirContent();
|
||||
return {};
|
||||
|
||||
const auto searchcaps = server.getSearchCapabilities(handle);
|
||||
if (searchcaps.empty())
|
||||
return UPnPDirContent();
|
||||
return {};
|
||||
|
||||
std::string cond;
|
||||
for (const auto &item : filter->GetItems()) {
|
||||
|
@@ -33,7 +33,6 @@
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
#include <string.h>
|
||||
|
@@ -282,7 +282,7 @@ InotifyUpdate::InotifyCallback(int wd, unsigned mask,
|
||||
(mask & IN_ISDIR) != 0) {
|
||||
/* a sub directory was changed: register those in
|
||||
inotify */
|
||||
const Path root_path = root->name;
|
||||
const auto root_path = root->name;
|
||||
|
||||
const auto path_fs = uri_fs.IsNull()
|
||||
? root_path
|
||||
|
@@ -34,7 +34,7 @@ UpdateQueueItem
|
||||
UpdateQueue::Pop() noexcept
|
||||
{
|
||||
if (update_queue.empty())
|
||||
return UpdateQueueItem();
|
||||
return {};
|
||||
|
||||
auto i = std::move(update_queue.front());
|
||||
update_queue.pop_front();
|
||||
|
@@ -36,7 +36,7 @@ UpdateRemoveService::RunDeferred() noexcept
|
||||
std::forward_list<std::string> copy;
|
||||
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
std::swap(uris, copy);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ UpdateRemoveService::Remove(std::string &&uri)
|
||||
bool was_empty;
|
||||
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
was_empty = uris.empty();
|
||||
uris.emplace_front(std::move(uri));
|
||||
}
|
||||
|
@@ -84,7 +84,7 @@ try {
|
||||
}
|
||||
|
||||
bool
|
||||
directory_child_access(Storage &storage, const Directory &directory,
|
||||
directory_child_access(const Storage &storage, const Directory &directory,
|
||||
std::string_view name, int mode) noexcept
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
@@ -55,7 +55,7 @@ directory_child_is_regular(Storage &storage, const Directory &directory,
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
bool
|
||||
directory_child_access(Storage &storage, const Directory &directory,
|
||||
directory_child_access(const Storage &storage, const Directory &directory,
|
||||
std::string_view name, int mode) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -188,8 +188,8 @@ UpdateWalk::UpdateRegularFile(Directory &directory,
|
||||
const char *name,
|
||||
const StorageFileInfo &info) noexcept
|
||||
{
|
||||
const auto suffix = uri_get_suffix(name);
|
||||
if (suffix.empty())
|
||||
const char *suffix = PathTraitsUTF8::GetFilenameSuffix(name);
|
||||
if (suffix == nullptr)
|
||||
return false;
|
||||
|
||||
return UpdateSongFile(directory, name, suffix, info) ||
|
||||
|
@@ -152,7 +152,7 @@ DecoderBridge::FlushChunk() noexcept
|
||||
if (!chunk->IsEmpty())
|
||||
dc.pipe->Push(std::move(chunk));
|
||||
|
||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
||||
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||
dc.client_cond.notify_one();
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ DecoderBridge::GetVirtualCommand() noexcept
|
||||
DecoderCommand
|
||||
DecoderBridge::LockGetVirtualCommand() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
||||
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||
return GetVirtualCommand();
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
|
||||
seekable);
|
||||
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
||||
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||
dc.SetReady(audio_format, seekable, duration);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ DecoderBridge::GetCommand() noexcept
|
||||
void
|
||||
DecoderBridge::CommandFinished() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
||||
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||
|
||||
assert(dc.command != DecoderCommand::NONE || initial_seek_running);
|
||||
assert(dc.command != DecoderCommand::SEEK ||
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "Command.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "MixRampInfo.hxx"
|
||||
#include "tag/MixRampInfo.hxx"
|
||||
#include "input/Handler.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "thread/Cond.hxx"
|
||||
@@ -231,7 +231,7 @@ public:
|
||||
|
||||
[[gnu::pure]]
|
||||
bool LockIsIdle() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return IsIdle();
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ public:
|
||||
|
||||
[[gnu::pure]]
|
||||
bool LockIsStarting() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return IsStarting();
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ public:
|
||||
|
||||
[[gnu::pure]]
|
||||
bool LockHasFailed() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return HasFailed();
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ public:
|
||||
* Like CheckRethrowError(), but locks and unlocks the object.
|
||||
*/
|
||||
void LockCheckRethrowError() const {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
CheckRethrowError();
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ private:
|
||||
}
|
||||
|
||||
void LockAsynchronousCommand(DecoderCommand cmd) noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
command = cmd;
|
||||
Signal();
|
||||
}
|
||||
|
@@ -35,8 +35,8 @@
|
||||
#include "DecoderPlugin.hxx"
|
||||
#include "ReplayGainInfo.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "tag/MixRampInfo.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "MixRampInfo.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "Chrono.hxx"
|
||||
|
||||
|
@@ -262,7 +262,7 @@ static void
|
||||
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
|
||||
{
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(bridge.dc.mutex);
|
||||
const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
|
||||
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
|
||||
/* ReplayGain is disabled */
|
||||
return;
|
||||
@@ -337,7 +337,7 @@ TryDecoderFile(DecoderBridge &bridge, Path path_fs, std::string_view suffix,
|
||||
DecoderControl &dc = bridge.dc;
|
||||
|
||||
if (plugin.file_decode != nullptr) {
|
||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
||||
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||
return decoder_file_decode(plugin, bridge, path_fs);
|
||||
} else if (plugin.stream_decode != nullptr) {
|
||||
std::unique_lock<Mutex> lock(dc.mutex);
|
||||
@@ -365,7 +365,7 @@ TryContainerDecoder(DecoderBridge &bridge, Path path_fs,
|
||||
bridge.Reset();
|
||||
|
||||
DecoderControl &dc = bridge.dc;
|
||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
||||
const std::scoped_lock<Mutex> protect(dc.mutex);
|
||||
return decoder_file_decode(plugin, bridge, path_fs);
|
||||
}
|
||||
|
||||
@@ -395,10 +395,12 @@ TryContainerDecoder(DecoderBridge &bridge, Path path_fs,
|
||||
static bool
|
||||
decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs)
|
||||
{
|
||||
const auto suffix = uri_get_suffix(uri_utf8);
|
||||
if (suffix.empty())
|
||||
const char *_suffix = PathTraitsUTF8::GetFilenameSuffix(uri_utf8);
|
||||
if (_suffix == nullptr)
|
||||
return false;
|
||||
|
||||
const std::string_view suffix{_suffix};
|
||||
|
||||
InputStreamPtr input_stream;
|
||||
|
||||
try {
|
||||
|
@@ -83,8 +83,8 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) noexcept
|
||||
static AFfileoffset
|
||||
audiofile_file_length(AFvirtualfile *vfile) noexcept
|
||||
{
|
||||
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||
InputStream &is = afis.is;
|
||||
const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||
const InputStream &is = afis.is;
|
||||
|
||||
return is.GetSize();
|
||||
}
|
||||
@@ -92,8 +92,8 @@ audiofile_file_length(AFvirtualfile *vfile) noexcept
|
||||
static AFfileoffset
|
||||
audiofile_file_tell(AFvirtualfile *vfile) noexcept
|
||||
{
|
||||
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||
InputStream &is = afis.is;
|
||||
const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||
const InputStream &is = afis.is;
|
||||
|
||||
return is.GetOffset();
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@
|
||||
#include "tag/Builder.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/ReplayGain.hxx"
|
||||
#include "tag/MixRamp.hxx"
|
||||
#include "tag/MixRampParser.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "pcm/CheckAudioFormat.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
@@ -502,7 +502,7 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
||||
FmtDebug(ffmpeg_domain, "codec '{}'",
|
||||
codec_descriptor->name);
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(codec_params.codec_id);
|
||||
const AVCodec *codec = avcodec_find_decoder(codec_params.codec_id);
|
||||
|
||||
if (!codec) {
|
||||
LogError(ffmpeg_domain, "Unsupported audio codec");
|
||||
|
@@ -78,7 +78,9 @@ FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc)
|
||||
if (flac_parse_replay_gain(rgi, vc))
|
||||
GetClient()->SubmitReplayGain(&rgi);
|
||||
|
||||
GetClient()->SubmitMixRamp(flac_parse_mixramp(vc));
|
||||
if (auto mix_ramp = flac_parse_mixramp(vc);
|
||||
mix_ramp.IsDefined())
|
||||
GetClient()->SubmitMixRamp(std::move(mix_ramp));
|
||||
|
||||
tag = flac_vorbis_comments_to_tag(&vc);
|
||||
}
|
||||
|
@@ -23,9 +23,10 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "tag/Id3Scan.hxx"
|
||||
#include "tag/Id3ReplayGain.hxx"
|
||||
#include "tag/Id3MixRamp.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/ReplayGain.hxx"
|
||||
#include "tag/MixRamp.hxx"
|
||||
#include "tag/MixRampParser.hxx"
|
||||
#include "pcm/CheckAudioFormat.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
@@ -268,35 +269,6 @@ MadDecoder::FillBuffer() noexcept
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_ID3TAG
|
||||
gcc_pure
|
||||
static MixRampInfo
|
||||
parse_id3_mixramp(struct id3_tag *tag) noexcept
|
||||
{
|
||||
MixRampInfo result;
|
||||
|
||||
struct id3_frame *frame;
|
||||
for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
|
||||
if (frame->nfields < 3)
|
||||
continue;
|
||||
|
||||
char *const key = (char *)
|
||||
id3_ucs4_latin1duplicate(id3_field_getstring
|
||||
(&frame->fields[1]));
|
||||
char *const value = (char *)
|
||||
id3_ucs4_latin1duplicate(id3_field_getstring
|
||||
(&frame->fields[2]));
|
||||
|
||||
ParseMixRampTag(result, key, value);
|
||||
|
||||
free(key);
|
||||
free(value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void
|
||||
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
||||
{
|
||||
@@ -310,7 +282,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
||||
id3_data = stream.this_frame;
|
||||
mad_stream_skip(&(stream), tagsize);
|
||||
} else {
|
||||
allocated.reset(new id3_byte_t[tagsize]);
|
||||
allocated = std::make_unique<id3_byte_t[]>(tagsize);
|
||||
memcpy(allocated.get(), stream.this_frame, count);
|
||||
mad_stream_skip(&(stream), count);
|
||||
|
||||
@@ -338,7 +310,9 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
||||
found_replay_gain = true;
|
||||
}
|
||||
|
||||
client->SubmitMixRamp(parse_id3_mixramp(id3_tag.get()));
|
||||
if (auto mix_ramp = Id3ToMixRampInfo(id3_tag.get());
|
||||
mix_ramp.IsDefined())
|
||||
client->SubmitMixRamp(std::move(mix_ramp));
|
||||
}
|
||||
|
||||
#else /* !ENABLE_ID3TAG */
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/Builder.hxx"
|
||||
#include "tag/ReplayGain.hxx"
|
||||
#include "tag/MixRamp.hxx"
|
||||
#include "tag/MixRampParser.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
|
@@ -137,7 +137,7 @@ SidplayGlobal::SidplayGlobal(const ConfigBlock &block)
|
||||
const auto kernal_path = block.GetPath("kernal");
|
||||
if (!kernal_path.IsNull())
|
||||
{
|
||||
kernal.reset(new uint8_t[rom_size]);
|
||||
kernal = std::make_unique<uint8_t[]>(rom_size);
|
||||
loadRom(kernal_path, kernal.get());
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ SidplayGlobal::SidplayGlobal(const ConfigBlock &block)
|
||||
const auto basic_path = block.GetPath("basic");
|
||||
if (!basic_path.IsNull())
|
||||
{
|
||||
basic.reset(new uint8_t[rom_size]);
|
||||
basic = std::make_unique<uint8_t[]>(rom_size);
|
||||
loadRom(basic_path, basic.get());
|
||||
}
|
||||
#endif
|
||||
|
@@ -68,7 +68,7 @@ private:
|
||||
exception = std::current_exception();
|
||||
}
|
||||
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
done = true;
|
||||
cond.notify_one();
|
||||
}
|
||||
|
@@ -52,6 +52,13 @@ EventLoop::EventLoop(
|
||||
|
||||
EventLoop::~EventLoop() noexcept
|
||||
{
|
||||
#if defined(HAVE_URING) && !defined(NDEBUG)
|
||||
/* if Run() was never called (maybe because startup failed and
|
||||
an exception is pending), we need to destruct the
|
||||
Uring::Manager here or else the assertions below fail */
|
||||
uring.reset();
|
||||
#endif
|
||||
|
||||
assert(defer.empty());
|
||||
assert(idle.empty());
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
@@ -175,6 +182,10 @@ EventLoop::HandleTimers() noexcept
|
||||
void
|
||||
EventLoop::AddDefer(DeferEvent &d) noexcept
|
||||
{
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
assert(!IsAlive() || IsInside());
|
||||
#endif
|
||||
|
||||
defer.push_back(d);
|
||||
again = true;
|
||||
}
|
||||
@@ -312,7 +323,7 @@ EventLoop::Run() noexcept
|
||||
/* try to handle DeferEvents without WakeFD
|
||||
overhead */
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
HandleInject();
|
||||
#endif
|
||||
|
||||
@@ -335,7 +346,7 @@ EventLoop::Run() noexcept
|
||||
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
busy = true;
|
||||
}
|
||||
#endif
|
||||
@@ -367,7 +378,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
|
||||
bool must_wake;
|
||||
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
if (d.IsPending())
|
||||
return;
|
||||
|
||||
@@ -386,7 +397,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
|
||||
void
|
||||
EventLoop::RemoveInject(InjectEvent &d) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
if (d.IsPending())
|
||||
inject.erase(inject.iterator_to(d));
|
||||
@@ -413,7 +424,7 @@ EventLoop::OnSocketReady([[maybe_unused]] unsigned flags) noexcept
|
||||
|
||||
wake_fd.Read();
|
||||
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
HandleInject();
|
||||
}
|
||||
|
||||
|
@@ -91,7 +91,7 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
bool IsDefined() const noexcept {
|
||||
[[nodiscard]] bool IsDefined() const noexcept {
|
||||
return event.IsDefined();
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,7 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <csignal>
|
||||
|
||||
@@ -62,7 +63,7 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
auto &GetEventLoop() const noexcept {
|
||||
[[nodiscard]] auto &GetEventLoop() const noexcept {
|
||||
return event.GetEventLoop();
|
||||
}
|
||||
|
||||
@@ -90,12 +91,12 @@ private:
|
||||
/* this should be enough - is it? */
|
||||
static constexpr unsigned MAX_SIGNAL = 64;
|
||||
|
||||
static SignalHandler signal_handlers[MAX_SIGNAL];
|
||||
static std::array<SignalHandler, MAX_SIGNAL> signal_handlers;
|
||||
|
||||
#ifdef USE_SIGNALFD
|
||||
static sigset_t signal_mask;
|
||||
#else
|
||||
static std::atomic_bool signal_pending[MAX_SIGNAL];
|
||||
static std::array<std::atomic_bool, MAX_SIGNAL> signal_pending;
|
||||
#endif
|
||||
|
||||
static Manual<SignalMonitor> monitor;
|
||||
@@ -153,7 +154,7 @@ void
|
||||
SignalMonitorFinish() noexcept
|
||||
{
|
||||
#ifdef USE_SIGNALFD
|
||||
std::fill_n(signal_handlers, MAX_SIGNAL, nullptr);
|
||||
signal_handlers = {};
|
||||
#else
|
||||
struct sigaction sa;
|
||||
sa.sa_flags = 0;
|
||||
@@ -167,7 +168,7 @@ SignalMonitorFinish() noexcept
|
||||
}
|
||||
}
|
||||
|
||||
std::fill_n(signal_pending, MAX_SIGNAL, false);
|
||||
std::fill(signal_pending.begin(), signal_pending.end(), false);
|
||||
#endif
|
||||
|
||||
monitor.Destruct();
|
||||
|
@@ -62,7 +62,7 @@ EventThread::Run() noexcept
|
||||
SetThreadRealtime();
|
||||
} catch (...) {
|
||||
FmtInfo(event_domain,
|
||||
"RTIOThread could not get realtime scheduling, continuing anyway: %s",
|
||||
"RTIOThread could not get realtime scheduling, continuing anyway: {}",
|
||||
std::current_exception());
|
||||
}
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
@@ -31,7 +31,7 @@ AllocatedPath::FromUTF8(std::string_view path_utf8) noexcept
|
||||
return FromFS(path_utf8);
|
||||
#else
|
||||
try {
|
||||
return AllocatedPath(::PathFromUTF8(path_utf8));
|
||||
return {::PathFromUTF8(path_utf8)};
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ AllocatedPath::FromUTF8Throw(std::string_view path_utf8)
|
||||
#ifdef FS_CHARSET_ALWAYS_UTF8
|
||||
return FromFS(path_utf8);
|
||||
#else
|
||||
return AllocatedPath(::PathFromUTF8(path_utf8));
|
||||
return {::PathFromUTF8(path_utf8)};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -26,7 +26,7 @@ Path::ToUTF8() const noexcept
|
||||
try {
|
||||
return ToUTF8Throw();
|
||||
} catch (...) {
|
||||
return std::string();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "StandardDirectory.hxx"
|
||||
#include "FileSystem.hxx"
|
||||
#include "XDG.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
#include "config.h"
|
||||
|
||||
#include <array>
|
||||
@@ -228,13 +229,12 @@ GetUserConfigDir() noexcept
|
||||
return GetStandardDir(CSIDL_LOCAL_APPDATA);
|
||||
#elif defined(USE_XDG)
|
||||
// Check for $XDG_CONFIG_HOME
|
||||
auto config_home = getenv("XDG_CONFIG_HOME");
|
||||
if (IsValidPathString(config_home) && IsValidDir(config_home))
|
||||
if (const auto config_home = getenv("XDG_CONFIG_HOME");
|
||||
IsValidPathString(config_home) && IsValidDir(config_home))
|
||||
return AllocatedPath::FromFS(config_home);
|
||||
|
||||
// Check for $HOME/.config
|
||||
auto home = GetHomeDir();
|
||||
if (!home.IsNull()) {
|
||||
if (const auto home = GetHomeDir(); !home.IsNull()) {
|
||||
auto fallback = home / Path::FromFS(".config");
|
||||
if (IsValidDir(fallback.c_str()))
|
||||
return fallback;
|
||||
@@ -265,17 +265,15 @@ GetUserCacheDir() noexcept
|
||||
{
|
||||
#ifdef USE_XDG
|
||||
// Check for $XDG_CACHE_HOME
|
||||
auto cache_home = getenv("XDG_CACHE_HOME");
|
||||
if (IsValidPathString(cache_home) && IsValidDir(cache_home))
|
||||
if (const auto cache_home = getenv("XDG_CACHE_HOME");
|
||||
IsValidPathString(cache_home) && IsValidDir(cache_home))
|
||||
return AllocatedPath::FromFS(cache_home);
|
||||
|
||||
// Check for $HOME/.cache
|
||||
auto home = GetHomeDir();
|
||||
if (!home.IsNull()) {
|
||||
auto fallback = home / Path::FromFS(".cache");
|
||||
if (IsValidDir(fallback.c_str()))
|
||||
if (const auto home = GetHomeDir(); !home.IsNull())
|
||||
if (auto fallback = home / Path::FromFS(".cache");
|
||||
IsValidDir(fallback.c_str()))
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
#elif defined(ANDROID)
|
||||
@@ -285,6 +283,38 @@ GetUserCacheDir() noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
GetUserRuntimeDir() noexcept
|
||||
{
|
||||
#ifdef USE_XDG
|
||||
return SafePathFromFS(getenv("XDG_RUNTIME_DIR"));
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
GetAppRuntimeDir() noexcept
|
||||
{
|
||||
#ifdef __linux__
|
||||
/* systemd specific; see systemd.exec(5) */
|
||||
if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
|
||||
if (auto dir = StringView{runtime_directory}.Split(':').first;
|
||||
!dir.empty())
|
||||
return AllocatedPath::FromFS(dir);
|
||||
#endif
|
||||
|
||||
#ifdef USE_XDG
|
||||
if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
|
||||
auto dir = user_dir / Path::FromFS("mpd");
|
||||
mkdir(dir.c_str(), 0700);
|
||||
return dir;
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
AllocatedPath
|
||||
@@ -317,11 +347,11 @@ AllocatedPath
|
||||
GetHomeDir() noexcept
|
||||
{
|
||||
#ifndef ANDROID
|
||||
auto home = getenv("HOME");
|
||||
if (IsValidPathString(home) && IsValidDir(home))
|
||||
if (const auto home = getenv("HOME");
|
||||
IsValidPathString(home) && IsValidDir(home))
|
||||
return AllocatedPath::FromFS(home);
|
||||
PasswdEntry pw;
|
||||
if (pw.ReadByUid(getuid()))
|
||||
|
||||
if (PasswdEntry pw; pw.ReadByUid(getuid()))
|
||||
return SafePathFromFS(pw->pw_dir);
|
||||
#endif
|
||||
return nullptr;
|
||||
@@ -334,8 +364,8 @@ GetHomeDir(const char *user_name) noexcept
|
||||
(void)user_name;
|
||||
#else
|
||||
assert(user_name != nullptr);
|
||||
PasswdEntry pw;
|
||||
if (pw.ReadByName(user_name))
|
||||
|
||||
if (PasswdEntry pw; pw.ReadByName(user_name))
|
||||
return SafePathFromFS(pw->pw_dir);
|
||||
#endif
|
||||
return nullptr;
|
||||
|
@@ -25,27 +25,44 @@
|
||||
/**
|
||||
* Obtains configuration directory for the current user.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetUserConfigDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains music directory for the current user.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetUserMusicDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains cache directory for the current user.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetUserCacheDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the runtime directory for the current user.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetUserRuntimeDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the runtime directory for this application.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetAppRuntimeDir() noexcept;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
/**
|
||||
* Obtains system configuration directory.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetSystemConfigDir() noexcept;
|
||||
|
||||
@@ -54,6 +71,7 @@ GetSystemConfigDir() noexcept;
|
||||
* Application base directory is a directory that contains 'bin' folder
|
||||
* for current executable.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetAppBaseDir() noexcept;
|
||||
|
||||
@@ -62,12 +80,14 @@ GetAppBaseDir() noexcept;
|
||||
/**
|
||||
* Obtains home directory for the current user.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetHomeDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains home directory for the specified user.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath
|
||||
GetHomeDir(const char *user_name) noexcept;
|
||||
|
||||
|
@@ -244,7 +244,7 @@ AsyncInputStream::AppendToBuffer(const void *data, size_t append_size) noexcept
|
||||
void
|
||||
AsyncInputStream::DeferredResume() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
try {
|
||||
Resume();
|
||||
@@ -257,7 +257,7 @@ AsyncInputStream::DeferredResume() noexcept
|
||||
void
|
||||
AsyncInputStream::DeferredSeek() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
if (seek_state != SeekState::SCHEDULED)
|
||||
return;
|
||||
|
||||
|
@@ -37,7 +37,7 @@ BufferingInputStream::BufferingInputStream(InputStreamPtr _input)
|
||||
BufferingInputStream::~BufferingInputStream() noexcept
|
||||
{
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
stop = true;
|
||||
wake_cond.notify_one();
|
||||
}
|
||||
|
@@ -71,12 +71,12 @@ input_stream_global_init(const ConfigData &config, EventLoop &event_loop)
|
||||
input_plugins_enabled[i] = true;
|
||||
} catch (const PluginUnconfigured &e) {
|
||||
FmtDebug(input_domain,
|
||||
"Input plugin '{}' is not configured: %s",
|
||||
"Input plugin '{}' is not configured: {}",
|
||||
plugin->name, e.what());
|
||||
continue;
|
||||
} catch (const PluginUnavailable &e) {
|
||||
FmtDebug(input_domain,
|
||||
"Input plugin '{}' is unavailable: %s",
|
||||
"Input plugin '{}' is unavailable: {}",
|
||||
plugin->name, e.what());
|
||||
continue;
|
||||
} catch (...) {
|
||||
|
@@ -97,7 +97,7 @@ InputStream::ReadTag() noexcept
|
||||
std::unique_ptr<Tag>
|
||||
InputStream::LockReadTag() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return ReadTag();
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ InputStream::LockReadFull(void *ptr, size_t _size)
|
||||
bool
|
||||
InputStream::LockIsEOF() const noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return IsEOF();
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
struct Tag;
|
||||
class InputStreamHandler;
|
||||
|
@@ -42,5 +42,6 @@ LastInputStream::OnCloseTimer() noexcept
|
||||
{
|
||||
assert(is);
|
||||
|
||||
uri.clear();
|
||||
is.reset();
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ ThreadInputStream::Stop() noexcept
|
||||
return;
|
||||
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
close = true;
|
||||
wake_cond.notify_one();
|
||||
}
|
||||
|
4
src/input/cache/Item.cxx
vendored
4
src/input/cache/Item.cxx
vendored
@@ -37,14 +37,14 @@ InputCacheItem::~InputCacheItem() noexcept
|
||||
void
|
||||
InputCacheItem::AddLease(InputCacheLease &lease) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
leases.push_back(lease);
|
||||
}
|
||||
|
||||
void
|
||||
InputCacheItem::RemoveLease(InputCacheLease &lease) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
auto i = leases.iterator_to(lease);
|
||||
if (i == next_lease)
|
||||
++next_lease;
|
||||
|
2
src/input/cache/Item.hxx
vendored
2
src/input/cache/Item.hxx
vendored
@@ -63,7 +63,7 @@ public:
|
||||
using BufferingInputStream::size;
|
||||
|
||||
bool IsInUse() const noexcept {
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
return !leases.empty();
|
||||
}
|
||||
|
||||
|
10
src/input/cache/Stream.cxx
vendored
10
src/input/cache/Stream.cxx
vendored
@@ -24,7 +24,7 @@ CacheInputStream::CacheInputStream(InputCacheLease _lease,
|
||||
:InputStream(_lease->GetUri(), _mutex),
|
||||
InputCacheLease(std::move(_lease))
|
||||
{
|
||||
auto &i = GetCacheItem();
|
||||
const auto &i = GetCacheItem();
|
||||
size = i.size();
|
||||
seekable = true;
|
||||
SetReady();
|
||||
@@ -36,7 +36,7 @@ CacheInputStream::Check()
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
auto &i = GetCacheItem();
|
||||
const std::lock_guard<Mutex> protect(i.mutex);
|
||||
const std::scoped_lock<Mutex> protect(i.mutex);
|
||||
|
||||
i.Check();
|
||||
}
|
||||
@@ -60,7 +60,7 @@ CacheInputStream::IsAvailable() const noexcept
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
auto &i = GetCacheItem();
|
||||
const std::lock_guard<Mutex> protect(i.mutex);
|
||||
const std::scoped_lock<Mutex> protect(i.mutex);
|
||||
|
||||
return i.IsAvailable(_offset);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ CacheInputStream::Read(std::unique_lock<Mutex> &lock,
|
||||
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
const std::lock_guard<Mutex> protect(i.mutex);
|
||||
const std::scoped_lock<Mutex> protect(i.mutex);
|
||||
|
||||
nbytes = i.Read(lock, _offset, ptr, read_size);
|
||||
}
|
||||
@@ -91,6 +91,6 @@ CacheInputStream::OnInputCacheAvailable() noexcept
|
||||
auto &i = GetCacheItem();
|
||||
const ScopeUnlock unlock(i.mutex);
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
InvokeOnAvailable();
|
||||
}
|
||||
|
@@ -26,13 +26,12 @@
|
||||
|
||||
#include "AlsaInputPlugin.hxx"
|
||||
#include "lib/alsa/NonBlock.hxx"
|
||||
#include "lib/alsa/Error.hxx"
|
||||
#include "lib/alsa/Format.hxx"
|
||||
#include "../InputPlugin.hxx"
|
||||
#include "../AsyncInputStream.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/DivideString.hxx"
|
||||
#include "pcm/AudioParser.hxx"
|
||||
@@ -238,7 +237,7 @@ AlsaInputStream::DispatchSockets() noexcept
|
||||
{
|
||||
non_block.DispatchSockets(*this, capture_handle);
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
auto w = PrepareWriteBuffer();
|
||||
const snd_pcm_uframes_t w_frames = w.size / frame_size;
|
||||
@@ -332,28 +331,23 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
|
||||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_any() failed");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
throw FormatRuntimeError("Cannot set access type (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_access() failed");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params,
|
||||
ToAlsaPcmFormat(audio_format.format))) < 0)
|
||||
throw FormatRuntimeError("Cannot set sample format (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set sample format");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_channels(capture_handle,
|
||||
hw_params, audio_format.channels)) < 0)
|
||||
throw FormatRuntimeError("Cannot set channels (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set channels");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_rate(capture_handle,
|
||||
hw_params, audio_format.sample_rate, 0)) < 0)
|
||||
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set sample rate");
|
||||
|
||||
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
||||
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
|
||||
@@ -388,26 +382,22 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
|
||||
int direction = -1;
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
|
||||
hw_params, &period_size, &direction)) < 0)
|
||||
throw FormatRuntimeError("Cannot set period size (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set period size");
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot set parameters (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params() failed");
|
||||
|
||||
snd_pcm_uframes_t alsa_buffer_size;
|
||||
err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa_buffer_size);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_buffer_size() failed");
|
||||
|
||||
snd_pcm_uframes_t alsa_period_size;
|
||||
err = snd_pcm_hw_params_get_period_size(hw_params, &alsa_period_size,
|
||||
nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_period_size() failed");
|
||||
|
||||
FmtDebug(alsa_input_domain, "buffer_size={} period_size={}",
|
||||
alsa_buffer_size, alsa_period_size);
|
||||
@@ -418,8 +408,7 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
|
||||
snd_pcm_sw_params_current(capture_handle, sw_params);
|
||||
|
||||
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
|
||||
throw FormatRuntimeError("unable to install sw params (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params() failed");
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -430,8 +419,9 @@ AlsaInputStream::OpenDevice(const SourceSpec &spec)
|
||||
if ((err = snd_pcm_open(&capture_handle, spec.GetDeviceName(),
|
||||
SND_PCM_STREAM_CAPTURE,
|
||||
SND_PCM_NONBLOCK | global_config.mode)) < 0)
|
||||
throw FormatRuntimeError("Failed to open device: %s (%s)",
|
||||
spec.GetDeviceName(), snd_strerror(err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to open device {}",
|
||||
spec.GetDeviceName()).c_str());
|
||||
|
||||
try {
|
||||
ConfigureCapture(spec.GetAudioFormat());
|
||||
|
@@ -238,7 +238,7 @@ CurlInputStream::OnHeaders(unsigned status,
|
||||
StringFormat<40>("got HTTP status %u",
|
||||
status).c_str());
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
if (IsSeekPending()) {
|
||||
/* don't update metadata while seeking */
|
||||
@@ -301,7 +301,7 @@ CurlInputStream::OnData(ConstBuffer<void> data)
|
||||
{
|
||||
assert(data.size > 0);
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
if (IsSeekPending())
|
||||
SeekDone();
|
||||
@@ -317,7 +317,7 @@ CurlInputStream::OnData(ConstBuffer<void> data)
|
||||
void
|
||||
CurlInputStream::OnEnd()
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
InvokeOnAvailable();
|
||||
|
||||
AsyncInputStream::SetClosed();
|
||||
@@ -326,7 +326,7 @@ CurlInputStream::OnEnd()
|
||||
void
|
||||
CurlInputStream::OnError(std::exception_ptr e) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
postponed_exception = std::move(e);
|
||||
|
||||
if (IsSeekPending())
|
||||
|
@@ -141,7 +141,7 @@ NfsInputStream::DoSeek(offset_type new_offset)
|
||||
void
|
||||
NfsInputStream::OnNfsFileOpen(uint64_t _size) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
if (reconnecting) {
|
||||
/* reconnect has succeeded */
|
||||
@@ -161,7 +161,7 @@ NfsInputStream::OnNfsFileOpen(uint64_t _size) noexcept
|
||||
void
|
||||
NfsInputStream::OnNfsFileRead(const void *data, size_t data_size) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
assert(!IsBufferFull());
|
||||
assert(IsBufferFull() == (GetBufferSpace() == 0));
|
||||
AppendToBuffer(data, data_size);
|
||||
@@ -174,7 +174,7 @@ NfsInputStream::OnNfsFileRead(const void *data, size_t data_size) noexcept
|
||||
void
|
||||
NfsInputStream::OnNfsFileError(std::exception_ptr &&e) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
if (IsPaused()) {
|
||||
/* while we're paused, don't report this error to the
|
||||
|
@@ -87,7 +87,7 @@ QobuzClient::StartLogin()
|
||||
void
|
||||
QobuzClient::AddLoginHandler(QobuzSessionHandler &h) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
assert(!h.is_linked());
|
||||
|
||||
const bool was_empty = handlers.empty();
|
||||
@@ -114,7 +114,7 @@ QobuzClient::AddLoginHandler(QobuzSessionHandler &h) noexcept
|
||||
QobuzSession
|
||||
QobuzClient::GetSession() const
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
if (error)
|
||||
std::rethrow_exception(error);
|
||||
@@ -129,7 +129,7 @@ void
|
||||
QobuzClient::OnQobuzLoginSuccess(QobuzSession &&_session) noexcept
|
||||
{
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
session = std::move(_session);
|
||||
login_request.reset();
|
||||
}
|
||||
@@ -141,7 +141,7 @@ void
|
||||
QobuzClient::OnQobuzLoginError(std::exception_ptr _error) noexcept
|
||||
{
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
error = std::move(_error);
|
||||
login_request.reset();
|
||||
}
|
||||
@@ -152,7 +152,7 @@ QobuzClient::OnQobuzLoginError(std::exception_ptr _error) noexcept
|
||||
void
|
||||
QobuzClient::InvokeHandlers() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
while (!handlers.empty()) {
|
||||
auto &h = handlers.front();
|
||||
handlers.pop_front();
|
||||
|
@@ -83,7 +83,7 @@ public:
|
||||
void AddLoginHandler(QobuzSessionHandler &h) noexcept;
|
||||
|
||||
void RemoveLoginHandler(QobuzSessionHandler &h) noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
if (h.is_linked())
|
||||
h.unlink();
|
||||
}
|
||||
|
@@ -84,7 +84,7 @@ private:
|
||||
void
|
||||
QobuzInputStream::OnQobuzSession() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
try {
|
||||
const auto session = qobuz_client->GetSession();
|
||||
@@ -103,7 +103,7 @@ QobuzInputStream::OnQobuzSession() noexcept
|
||||
void
|
||||
QobuzInputStream::OnQobuzTrackSuccess(std::string url) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
track_request.reset();
|
||||
|
||||
try {
|
||||
@@ -117,7 +117,7 @@ QobuzInputStream::OnQobuzTrackSuccess(std::string url) noexcept
|
||||
void
|
||||
QobuzInputStream::OnQobuzTrackError(std::exception_ptr e) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
track_request.reset();
|
||||
|
||||
Failed(e);
|
||||
|
@@ -149,7 +149,7 @@ UringInputStream::OnRead(std::unique_ptr<std::byte[]> data,
|
||||
{
|
||||
read_operation.reset();
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
if (nbytes == 0) {
|
||||
postponed_exception = std::make_exception_ptr(std::runtime_error("Premature end of file"));
|
||||
@@ -170,7 +170,7 @@ UringInputStream::OnReadError(int error) noexcept
|
||||
{
|
||||
read_operation.reset();
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
postponed_exception = std::make_exception_ptr(MakeErrno(error, "Read failed"));
|
||||
InvokeOnAvailable();
|
||||
|
@@ -30,12 +30,7 @@ namespace Alsa {
|
||||
AllowedFormat::AllowedFormat(StringView s)
|
||||
{
|
||||
#ifdef ENABLE_DSD
|
||||
const StringView dop_tail("=dop");
|
||||
if (s.EndsWith(dop_tail)) {
|
||||
dop = true;
|
||||
s.size -= dop_tail.size;
|
||||
} else
|
||||
dop = false;
|
||||
dop = s.RemoveSuffix("=dop");
|
||||
#endif
|
||||
|
||||
char buffer[64];
|
||||
@@ -54,7 +49,7 @@ AllowedFormat::AllowedFormat(StringView s)
|
||||
}
|
||||
|
||||
std::forward_list<AllowedFormat>
|
||||
AllowedFormat::ParseList(StringView s)
|
||||
AllowedFormat::ParseList(std::string_view s)
|
||||
{
|
||||
std::forward_list<AllowedFormat> list;
|
||||
auto tail = list.before_begin();
|
||||
|
@@ -52,7 +52,7 @@ struct AllowedFormat {
|
||||
*
|
||||
* Throws std::runtime_error on error.
|
||||
*/
|
||||
static std::forward_list<AllowedFormat> ParseList(StringView s);
|
||||
static std::forward_list<AllowedFormat> ParseList(std::string_view s);
|
||||
};
|
||||
|
||||
std::string
|
||||
|
44
src/lib/alsa/Error.cxx
Normal file
44
src/lib/alsa/Error.cxx
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Error.hxx"
|
||||
|
||||
#include <alsa/error.h>
|
||||
|
||||
namespace Alsa {
|
||||
|
||||
ErrorCategory error_category;
|
||||
|
||||
std::string
|
||||
ErrorCategory::message(int condition) const
|
||||
{
|
||||
return snd_strerror(condition);
|
||||
}
|
||||
|
||||
} // namespace Alsa
|
53
src/lib/alsa/Error.hxx
Normal file
53
src/lib/alsa/Error.hxx
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace Alsa {
|
||||
|
||||
class ErrorCategory final : public std::error_category {
|
||||
public:
|
||||
const char *name() const noexcept override {
|
||||
return "libasound";
|
||||
}
|
||||
|
||||
std::string message(int condition) const override;
|
||||
};
|
||||
|
||||
extern ErrorCategory error_category;
|
||||
|
||||
inline std::system_error
|
||||
MakeError(int error, const char *msg) noexcept
|
||||
{
|
||||
return std::system_error(error, error_category, msg);
|
||||
}
|
||||
|
||||
} // namespace Avahi
|
@@ -18,7 +18,9 @@
|
||||
*/
|
||||
|
||||
#include "HwSetup.hxx"
|
||||
#include "Error.hxx"
|
||||
#include "Format.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@@ -185,29 +187,27 @@ SetupHw(snd_pcm_t *pcm,
|
||||
/* configure HW params */
|
||||
err = snd_pcm_hw_params_any(pcm, hwparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_any() failed");
|
||||
|
||||
err = snd_pcm_hw_params_set_access(pcm, hwparams,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_access() failed");
|
||||
|
||||
err = SetupSampleFormat(pcm, hwparams,
|
||||
audio_format.format, params);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure format %s: %s",
|
||||
sample_format_to_string(audio_format.format),
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to configure format {}",
|
||||
audio_format.format).c_str());
|
||||
|
||||
unsigned int channels = audio_format.channels;
|
||||
err = snd_pcm_hw_params_set_channels_near(pcm, hwparams,
|
||||
&channels);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure %i channels: %s",
|
||||
(int)audio_format.channels,
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to configure {} channels",
|
||||
audio_format.channels).c_str());
|
||||
|
||||
audio_format.channels = (int8_t)channels;
|
||||
|
||||
@@ -218,9 +218,9 @@ SetupHw(snd_pcm_t *pcm,
|
||||
err = snd_pcm_hw_params_set_rate_near(pcm, hwparams,
|
||||
&output_sample_rate, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
||||
requested_sample_rate,
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to configure sample rate {} Hz",
|
||||
requested_sample_rate).c_str());
|
||||
|
||||
if (output_sample_rate == 0)
|
||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
|
||||
@@ -253,8 +253,7 @@ SetupHw(snd_pcm_t *pcm,
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(pcm, hwparams,
|
||||
&buffer_time, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_buffer_time_near() failed");
|
||||
} else {
|
||||
err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
|
||||
nullptr);
|
||||
@@ -275,32 +274,27 @@ SetupHw(snd_pcm_t *pcm,
|
||||
err = snd_pcm_hw_params_set_period_time_near(pcm, hwparams,
|
||||
&period_time, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_period_time_near() failed");
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params(pcm, hwparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params() failed");
|
||||
|
||||
HwResult result;
|
||||
|
||||
err = snd_pcm_hw_params_get_format(hwparams, &result.format);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_format() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_format() failed");
|
||||
|
||||
err = snd_pcm_hw_params_get_buffer_size(hwparams, &result.buffer_size);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_buffer_size() failed");
|
||||
|
||||
err = snd_pcm_hw_params_get_period_size(hwparams, &result.period_size,
|
||||
nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_period_size() failed");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "NonBlock.hxx"
|
||||
#include "Error.hxx"
|
||||
#include "event/MultiSocketMonitor.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
@@ -29,8 +30,7 @@ AlsaNonBlockPcm::PrepareSockets(MultiSocketMonitor &m, snd_pcm_t *pcm)
|
||||
if (count == 0)
|
||||
throw std::runtime_error("snd_pcm_poll_descriptors_count() failed");
|
||||
else
|
||||
throw FormatRuntimeError("snd_pcm_poll_descriptors_count() failed: %s",
|
||||
snd_strerror(-count));
|
||||
throw Alsa::MakeError(count, "snd_pcm_poll_descriptors_count() failed");
|
||||
}
|
||||
|
||||
struct pollfd *pfds = pfd_buffer.Get(count);
|
||||
@@ -40,8 +40,7 @@ AlsaNonBlockPcm::PrepareSockets(MultiSocketMonitor &m, snd_pcm_t *pcm)
|
||||
if (count == 0)
|
||||
throw std::runtime_error("snd_pcm_poll_descriptors() failed");
|
||||
else
|
||||
throw FormatRuntimeError("snd_pcm_poll_descriptors() failed: %s",
|
||||
snd_strerror(-count));
|
||||
throw Alsa::MakeError(count, "snd_pcm_poll_descriptors() failed");
|
||||
}
|
||||
|
||||
m.ReplaceSocketList(pfds, count);
|
||||
@@ -71,8 +70,7 @@ AlsaNonBlockPcm::DispatchSockets(MultiSocketMonitor &m,
|
||||
unsigned short dummy;
|
||||
int err = snd_pcm_poll_descriptors_revents(pcm, pfds, i - pfds, &dummy);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_poll_descriptors_revents() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_poll_descriptors_revents() failed");
|
||||
}
|
||||
|
||||
Event::Duration
|
||||
|
@@ -14,6 +14,7 @@ conf.set('ENABLE_ALSA', true)
|
||||
alsa = static_library(
|
||||
'alsa',
|
||||
'Version.cxx',
|
||||
'Error.cxx',
|
||||
'AllowedFormat.cxx',
|
||||
'HwSetup.cxx',
|
||||
'NonBlock.cxx',
|
||||
|
@@ -61,7 +61,7 @@ public:
|
||||
CurlSocket(const CurlSocket &) = delete;
|
||||
CurlSocket &operator=(const CurlSocket &) = delete;
|
||||
|
||||
auto &GetEventLoop() const noexcept {
|
||||
[[nodiscard]] auto &GetEventLoop() const noexcept {
|
||||
return socket_event.GetEventLoop();
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
void *userp, void *socketp) noexcept;
|
||||
|
||||
private:
|
||||
SocketDescriptor GetSocket() const noexcept {
|
||||
[[nodiscard]] SocketDescriptor GetSocket() const noexcept {
|
||||
return socket_event.GetSocket();
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user