Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12147f6d58 | ||
|
|
40bc60d6ae | ||
|
|
7778210269 | ||
|
|
6229210d51 | ||
|
|
5d0d5b5d97 | ||
|
|
1aa3c1e543 | ||
|
|
b90e32fe4e | ||
|
|
1f4df2a64d | ||
|
|
2efc1db6a9 | ||
|
|
e2d4654e20 | ||
|
|
2b8f1170a6 | ||
|
|
5c4743441e | ||
|
|
cb288439a4 | ||
|
|
69f741e8a6 | ||
|
|
4b4f47002b | ||
|
|
615c301961 | ||
|
|
dc07180e48 | ||
|
|
d3b235bab5 | ||
|
|
7c920ddebe | ||
|
|
bbc088ae4e | ||
|
|
fe195257d8 | ||
|
|
57d5df8118 | ||
|
|
59792cb0b8 | ||
|
|
cc557c4d60 | ||
|
|
956c5faebb | ||
|
|
cd0396c1f1 | ||
|
|
79f9b268bb | ||
|
|
b45f3c8deb | ||
|
|
f8a8de87e4 | ||
|
|
2183f0553c | ||
|
|
1f28790476 | ||
|
|
c8dae95eff | ||
|
|
547a084c7e | ||
|
|
493677ff81 | ||
|
|
6b430ba271 | ||
|
|
bc6924d303 | ||
|
|
02b00f9146 | ||
|
|
e807ed5870 | ||
|
|
f08944253b | ||
|
|
792d6584b9 | ||
|
|
7b45d01462 | ||
|
|
5c17b2966a | ||
|
|
0c54f29446 | ||
|
|
9c3cf39fdd | ||
|
|
d2fb229685 | ||
|
|
f55bc6682f | ||
|
|
6857286b42 | ||
|
|
c0d5bd2048 | ||
|
|
666e5d7904 | ||
|
|
3613407ac5 | ||
|
|
c32dceb4d4 | ||
|
|
5573e78364 | ||
|
|
807a19889f | ||
|
|
df7242de91 | ||
|
|
d62426f168 | ||
|
|
1714cf3417 | ||
|
|
1080c917be | ||
|
|
8eb3164878 | ||
|
|
915c5442d1 | ||
|
|
be0360d5e8 | ||
|
|
4d6ae6ffdd | ||
|
|
ecee6f415b | ||
|
|
47680f936b | ||
|
|
2d7181105d | ||
|
|
9bdc75524b | ||
|
|
2f6ceb4949 | ||
|
|
cd933aa35f | ||
|
|
138738075b | ||
|
|
2ee57f9b0d | ||
|
|
5a5655b790 | ||
|
|
b88d1e6820 | ||
|
|
19d2864c34 | ||
|
|
29e3a17f26 | ||
|
|
252e9f736f | ||
|
|
5d08988dda | ||
|
|
47ca4246aa | ||
|
|
f8338d4f00 | ||
|
|
5cf6032c90 | ||
|
|
8d8b77412d | ||
|
|
fd9114e7e2 | ||
|
|
a3fba2f8f7 | ||
|
|
e2b671f1b2 | ||
|
|
2a35fbe29e | ||
|
|
81cde72fd0 | ||
|
|
bf9ffba4f7 | ||
|
|
c975d8b943 | ||
|
|
2730f91872 | ||
|
|
97ca85e155 | ||
|
|
39bb4c5871 | ||
|
|
bdceb90c59 | ||
|
|
8bd1b5228c | ||
|
|
a009e95afd | ||
|
|
32aafb3572 | ||
|
|
b577783cf0 | ||
|
|
aa7b872a14 | ||
|
|
c6f7f57776 | ||
|
|
106ad08cd2 | ||
|
|
0341ca1b6a | ||
|
|
7581ea55db | ||
|
|
fc9cee38d8 | ||
|
|
b175e4128d | ||
|
|
97b07798b0 | ||
|
|
112fcd206d | ||
|
|
11d1f56062 | ||
|
|
bd840d4638 | ||
|
|
c3d393f214 | ||
|
|
f88fc0ca1a | ||
|
|
fb8d8242ab | ||
|
|
f2a3dfd700 | ||
|
|
85f9863e0a | ||
|
|
83572701f4 | ||
|
|
fa7d7e9187 | ||
|
|
f818cde32c | ||
|
|
9da93cd887 | ||
|
|
026e7ea32a | ||
|
|
9659d19718 | ||
|
|
50d35c9677 | ||
|
|
4260e78861 | ||
|
|
7342ae2e33 | ||
|
|
35dbc1a90c | ||
|
|
c7a4355153 | ||
|
|
33a84a8ca2 | ||
|
|
1d04490ed3 | ||
|
|
4a30c2d79c | ||
|
|
83072d6b9c | ||
|
|
c779fc37eb | ||
|
|
e08c13ad7e | ||
|
|
2c82a6b2e0 | ||
|
|
3929f17aef | ||
|
|
ee39af3419 | ||
|
|
3882a5a263 | ||
|
|
ac06088948 | ||
|
|
a757eebfbb | ||
|
|
2be4f89555 | ||
|
|
4a5c7d8261 |
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: MaxK
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
||||||
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -18,5 +18,9 @@ about: Create a bug report
|
|||||||
<!-- Paste the output of "mpd --version" here -->
|
<!-- Paste the output of "mpd --version" here -->
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
<!-- Paste your MPD configuration here -->
|
||||||
|
|
||||||
|
|
||||||
## Log
|
## Log
|
||||||
<!-- Paste relevant portions of the log file here (--verbose) -->
|
<!-- Paste relevant portions of the log file here (--verbose) -->
|
||||||
|
|||||||
9
.github/ISSUE_TEMPLATE/question.md
vendored
9
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
name: Question
|
|
||||||
about: Ask a question about MPD
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Before you ask a question on GitHub, please read MPD's
|
|
||||||
documentation. A copy is available at
|
|
||||||
https://www.musicpd.org/doc/html/ -->
|
|
||||||
## Question
|
|
||||||
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
@@ -41,7 +41,8 @@ jobs:
|
|||||||
key: linux
|
key: linux
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt install -y --no-install-recommends \
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends \
|
||||||
g++-10 libfmt-dev libboost-dev \
|
g++-10 libfmt-dev libboost-dev \
|
||||||
libgtest-dev \
|
libgtest-dev \
|
||||||
libpcre2-dev \
|
libpcre2-dev \
|
||||||
@@ -73,19 +74,30 @@ jobs:
|
|||||||
libgcrypt20-dev
|
libgcrypt20-dev
|
||||||
|
|
||||||
- name: Full Build
|
- name: Full Build
|
||||||
|
uses: BSFishy/meson-build@v1.0.3
|
||||||
|
with:
|
||||||
|
action: build
|
||||||
|
directory: output/full
|
||||||
|
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||||
|
options: --verbose
|
||||||
|
meson-version: 0.56.0
|
||||||
|
|
||||||
|
- name: Unit Tests
|
||||||
uses: BSFishy/meson-build@v1.0.3
|
uses: BSFishy/meson-build@v1.0.3
|
||||||
with:
|
with:
|
||||||
action: test
|
action: test
|
||||||
directory: output/full
|
directory: output/full
|
||||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||||
|
options: --verbose
|
||||||
meson-version: 0.56.0
|
meson-version: 0.56.0
|
||||||
|
|
||||||
- name: Mini Build
|
- name: Mini Build
|
||||||
uses: BSFishy/meson-build@v1.0.3
|
uses: BSFishy/meson-build@v1.0.3
|
||||||
with:
|
with:
|
||||||
action: test
|
action: build
|
||||||
directory: output/mini
|
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
|
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
|
||||||
|
options: --verbose
|
||||||
meson-version: 0.56.0
|
meson-version: 0.56.0
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
@@ -124,10 +136,20 @@ jobs:
|
|||||||
wavpack \
|
wavpack \
|
||||||
libmpdclient
|
libmpdclient
|
||||||
|
|
||||||
- name: Meson Build
|
- name: Build
|
||||||
|
uses: BSFishy/meson-build@v1.0.3
|
||||||
|
with:
|
||||||
|
action: build
|
||||||
|
directory: output
|
||||||
|
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||||
|
options: --verbose
|
||||||
|
meson-version: 0.56.0
|
||||||
|
|
||||||
|
- name: Unit Tests
|
||||||
uses: BSFishy/meson-build@v1.0.3
|
uses: BSFishy/meson-build@v1.0.3
|
||||||
with:
|
with:
|
||||||
action: test
|
action: test
|
||||||
directory: output
|
directory: output
|
||||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||||
|
options: --verbose
|
||||||
meson-version: 0.56.0
|
meson-version: 0.56.0
|
||||||
|
|||||||
48
NEWS
48
NEWS
@@ -1,3 +1,51 @@
|
|||||||
|
ver 0.23.9 (2022/08/18)
|
||||||
|
* input
|
||||||
|
- cdio_paranoia: add options "mode" and "skip"
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: support FFmpeg 5.1
|
||||||
|
* filter
|
||||||
|
- replay gain: fix delayed volume display with handler=mixer
|
||||||
|
* output
|
||||||
|
- pipewire: set app icon
|
||||||
|
* fix bogus volume levels with multiple partitions
|
||||||
|
* improve iconv detection
|
||||||
|
* macOS: fix macOS 10 build problem (0.23.8 regression)
|
||||||
|
* Android
|
||||||
|
- load mpd.conf from app data directory
|
||||||
|
|
||||||
|
ver 0.23.8 (2022/07/09)
|
||||||
|
* storage
|
||||||
|
- curl: fix crash if web server does not understand WebDAV
|
||||||
|
* input
|
||||||
|
- cdio_paranoia: fix crash if no drive was found
|
||||||
|
- cdio_paranoia: faster cancellation
|
||||||
|
- cdio_paranoia: don't scan for replay gain tags
|
||||||
|
- pipewire: fix playback of very short tracks
|
||||||
|
- pipewire: drop all buffers before manual song change
|
||||||
|
- pipewire: fix stuttering after manual song change
|
||||||
|
- snapcast: fix busy loop while paused
|
||||||
|
- snapcast: fix stuttering after resuming playback
|
||||||
|
* mixer
|
||||||
|
- better error messages
|
||||||
|
- alsa: fix setting volume before playback starts
|
||||||
|
- pipewire: fix crash bug
|
||||||
|
- pipewire: fix volume change events with PipeWire 0.3.53
|
||||||
|
- pipewire: don't force initial volume=100%
|
||||||
|
* support libfmt 9
|
||||||
|
|
||||||
|
ver 0.23.7 (2022/05/09)
|
||||||
|
* database
|
||||||
|
- upnp: support pupnp 1.14
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: fix HLS seeking
|
||||||
|
- opus: fix missing song length on high-latency files
|
||||||
|
* output
|
||||||
|
- shout: require at least libshout 2.4.0
|
||||||
|
* mixer
|
||||||
|
- pipewire: fix volume restore
|
||||||
|
- software: update volume of disabled outputs
|
||||||
|
* support libiconv
|
||||||
|
|
||||||
ver 0.23.6 (2022/03/14)
|
ver 0.23.6 (2022/03/14)
|
||||||
* protocol
|
* protocol
|
||||||
- support filename "cover.webp" for "albumart" command
|
- support filename "cover.webp" for "albumart" command
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="65"
|
android:versionCode="68"
|
||||||
android:versionName="0.23.5">
|
android:versionName="0.23.9">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
<uses-feature android:name="android.software.leanback"
|
<uses-feature android:name="android.software.leanback"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application android:allowBackup="true"
|
<application android:allowBackup="true"
|
||||||
|
android:debuggable="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:banner="@drawable/icon"
|
android:banner="@drawable/icon"
|
||||||
|
|||||||
@@ -12,18 +12,30 @@ unsigned_apk = custom_target(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
aligned_apk = custom_target(
|
||||||
|
'mpd-aligned.apk',
|
||||||
|
output: 'mpd-aligned.apk',
|
||||||
|
input: unsigned_apk,
|
||||||
|
command: [
|
||||||
|
android_zipalign,
|
||||||
|
'-f', '4',
|
||||||
|
'@INPUT@', '@OUTPUT@',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
if get_option('android_debug_keystore') != ''
|
if get_option('android_debug_keystore') != ''
|
||||||
debug_apk = custom_target(
|
debug_apk = custom_target(
|
||||||
'mpd-debug.apk',
|
'mpd-debug.apk',
|
||||||
output: 'mpd-debug.apk',
|
output: 'mpd-debug.apk',
|
||||||
input: unsigned_apk,
|
input: aligned_apk,
|
||||||
command: [
|
command: [
|
||||||
jarsigner,
|
apksigner, 'sign',
|
||||||
'-keystore', get_option('android_debug_keystore'),
|
'--in', '@INPUT@',
|
||||||
'-storepass', 'android',
|
'--out', '@OUTPUT@',
|
||||||
'-signedjar', '@OUTPUT@',
|
'--debuggable-apk-permitted',
|
||||||
'@INPUT@',
|
'-ks', get_option('android_debug_keystore'),
|
||||||
'androiddebugkey',
|
'--ks-key-alias', 'androiddebugkey',
|
||||||
|
'--ks-pass', 'pass:android',
|
||||||
],
|
],
|
||||||
build_by_default: true
|
build_by_default: true
|
||||||
)
|
)
|
||||||
@@ -31,29 +43,16 @@ endif
|
|||||||
|
|
||||||
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
|
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
|
||||||
unaligned_apk = custom_target(
|
unaligned_apk = custom_target(
|
||||||
'mpd-unaligned.apk',
|
|
||||||
output: 'mpd-unaligned.apk',
|
|
||||||
input: unsigned_apk,
|
|
||||||
command: [
|
|
||||||
jarsigner,
|
|
||||||
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
|
|
||||||
'-keystore', get_option('android_keystore'),
|
|
||||||
'-storepass', get_option('android_keypass'),
|
|
||||||
'-signedjar', '@OUTPUT@',
|
|
||||||
'@INPUT@',
|
|
||||||
get_option('android_keyalias'),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
apk = custom_target(
|
|
||||||
'mpd.apk',
|
'mpd.apk',
|
||||||
output: 'mpd.apk',
|
output: 'mpd.apk',
|
||||||
input: unaligned_apk,
|
input: aligned_apk,
|
||||||
command: [
|
command: [
|
||||||
android_zipalign,
|
apksigner, 'sign',
|
||||||
'-f', '4',
|
'--in', '@INPUT@',
|
||||||
'@INPUT@', '@OUTPUT@',
|
'--out', '@OUTPUT@',
|
||||||
|
'-ks', get_option('android_keystore'),
|
||||||
|
'--ks-key-alias', get_option('android_keyalias'),
|
||||||
|
'--ks-pass', 'pass:' + get_option('android_keypass'),
|
||||||
],
|
],
|
||||||
build_by_default: true
|
|
||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env -S python3 -u
|
||||||
|
|
||||||
import os, os.path
|
import os, os.path
|
||||||
import sys, subprocess
|
import sys, subprocess
|
||||||
|
|||||||
54
android/gdb.sh
Executable file
54
android/gdb.sh
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script need the following modification in ANDROID_NDK in order to attach
|
||||||
|
# to the good :main pid
|
||||||
|
#--- a/prebuilt/linux-x86_64/bin/ndk-gdb.py
|
||||||
|
#+++ b/prebuilt/linux-x86_64/bin/ndk-gdb.py
|
||||||
|
#@@ -669,7 +669,7 @@
|
||||||
|
# log("Sleeping for {} seconds.".format(args.delay))
|
||||||
|
# time.sleep(args.delay)
|
||||||
|
#
|
||||||
|
#- pids = gdbrunner.get_pids(device, pkg_name)
|
||||||
|
#+ pids = gdbrunner.get_pids(device, pkg_name + ":main")
|
||||||
|
# if len(pids) == 0:
|
||||||
|
# error("Failed to find running process '{}'".format(pkg_name))
|
||||||
|
# if len(pids) > 1:
|
||||||
|
|
||||||
|
SCRIPT_PATH=$(dirname $0)
|
||||||
|
BUILD_PATH="`pwd`"
|
||||||
|
TMP_PATH="$BUILD_PATH/gdb"
|
||||||
|
NDK_GDB_ARGS="--force"
|
||||||
|
ANDROID_NDK=$1
|
||||||
|
|
||||||
|
if [ ! -f $ANDROID_NDK/source.properties ];then
|
||||||
|
echo "usage: $0 ANDROID_NDK"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f $BUILD_PATH/libmpd.so ];then
|
||||||
|
echo "This script need to be executed from the android build directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$TMP_PATH"
|
||||||
|
mkdir -p "$TMP_PATH"
|
||||||
|
|
||||||
|
ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml"
|
||||||
|
ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1`
|
||||||
|
|
||||||
|
if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then
|
||||||
|
echo "Invalid manifest/ABI, did you try building first ?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$TMP_PATH"/jni
|
||||||
|
touch "$TMP_PATH"/jni/Android.mk
|
||||||
|
echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk
|
||||||
|
|
||||||
|
DEST=obj/local/$ABI
|
||||||
|
mkdir -p "$TMP_PATH/$DEST"
|
||||||
|
|
||||||
|
cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST"
|
||||||
|
cp "$ANDROID_MANIFEST" "$TMP_PATH"
|
||||||
|
|
||||||
|
(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS)
|
||||||
@@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
|
|||||||
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
|
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
|
||||||
|
|
||||||
javac = find_program('javac')
|
javac = find_program('javac')
|
||||||
jarsigner = find_program('jarsigner')
|
apksigner = find_program('apksigner')
|
||||||
rsvg_convert = find_program('rsvg-convert')
|
rsvg_convert = find_program('rsvg-convert')
|
||||||
convert = find_program('convert')
|
convert = find_program('convert')
|
||||||
zip = find_program('zip')
|
zip = find_program('zip')
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.23.6'
|
version = '0.23.9'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
#release = version + '~git'
|
#release = version + '~git'
|
||||||
|
|
||||||
|
|||||||
@@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
|
|||||||
- If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
|
- If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
|
||||||
* - **speed N**
|
* - **speed N**
|
||||||
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
|
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
|
||||||
|
* - **mode disable|overlap|full**
|
||||||
|
- Set the paranoia mode; ``disable`` means no fixups, ``overlap``
|
||||||
|
performs overlapped reads, and ``full`` enables all options.
|
||||||
|
* - **skip yes|no**
|
||||||
|
- If set to ``no``, then never skip failed reads.
|
||||||
|
|
||||||
curl
|
curl
|
||||||
----
|
----
|
||||||
|
|||||||
@@ -545,6 +545,13 @@ Playback options
|
|||||||
Sets repeat state to ``STATE``,
|
Sets repeat state to ``STATE``,
|
||||||
``STATE`` should be 0 or 1.
|
``STATE`` should be 0 or 1.
|
||||||
|
|
||||||
|
If enabled, MPD keeps repeating the whole queue (:ref:`single mode
|
||||||
|
<command_single>` disabled) or the current song (:ref:`single mode
|
||||||
|
<command_single>` enabled).
|
||||||
|
|
||||||
|
If :ref:`random mode <command_random>` is also enabled, the
|
||||||
|
playback order will be shuffled each time the queue gets repeated.
|
||||||
|
|
||||||
.. _command_setvol:
|
.. _command_setvol:
|
||||||
|
|
||||||
:command:`setvol {VOL}`
|
:command:`setvol {VOL}`
|
||||||
|
|||||||
22
doc/user.rst
22
doc/user.rst
@@ -36,7 +36,9 @@ Installing on Android
|
|||||||
|
|
||||||
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
|
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
|
||||||
|
|
||||||
If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function).
|
If you need to tweak the configuration, you can create a file called
|
||||||
|
:file:`mpd.conf` in MPD's data directory on the external storage
|
||||||
|
(usually :file:`Android/data/org.musicpd/files/mpd.conf`).
|
||||||
|
|
||||||
ALSA is not available on Android; only the :ref:`OpenSL ES
|
ALSA is not available on Android; only the :ref:`OpenSL ES
|
||||||
<sles_output>` output plugin can be used for local playback.
|
<sles_output>` output plugin can be used for local playback.
|
||||||
@@ -301,7 +303,7 @@ Configuring neighbor plugins
|
|||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
All neighbor plugins are disabled by default to avoid unwanted
|
All neighbor plugins are disabled by default to avoid unwanted
|
||||||
overhead. To enable (and configure) a plugin, add a :code:`neighbor`
|
overhead. To enable (and configure) a plugin, add a :code:`neighbors`
|
||||||
block to :file:`mpd.conf`:
|
block to :file:`mpd.conf`:
|
||||||
|
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
@@ -538,7 +540,7 @@ The following table lists the playlist_plugin options valid for all plugins:
|
|||||||
|
|
||||||
* - Name
|
* - Name
|
||||||
- Description
|
- Description
|
||||||
* - **plugin**
|
* - **name**
|
||||||
- The name of the plugin
|
- The name of the plugin
|
||||||
* - **enabled yes|no**
|
* - **enabled yes|no**
|
||||||
- Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled.
|
- Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled.
|
||||||
@@ -1063,7 +1065,19 @@ The "music directory" is where you store your music files. :program:`MPD` stores
|
|||||||
|
|
||||||
Depending on the size of your music collection and the speed of the storage, this can take a while.
|
Depending on the size of your music collection and the speed of the storage, this can take a while.
|
||||||
|
|
||||||
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
|
To exclude a file from the update, create a file called
|
||||||
|
:file:`.mpdignore` in its parent directory. Each line of that file
|
||||||
|
may contain a list of shell wildcards. Matching files (or
|
||||||
|
directories) in the current directory and all subdirectories are
|
||||||
|
excluded. Example::
|
||||||
|
|
||||||
|
*.opus
|
||||||
|
99*
|
||||||
|
|
||||||
|
Subject to pattern matching is the file/directory name. It is (not
|
||||||
|
yet) possible to match nested path names, e.g. something like
|
||||||
|
``foo/*.flac`` is not possible.
|
||||||
|
|
||||||
|
|
||||||
Mounting other storages into the music directory
|
Mounting other storages into the music directory
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
project(
|
project(
|
||||||
'mpd',
|
'mpd',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.23.6',
|
version: '0.23.9',
|
||||||
meson_version: '>= 0.56.0',
|
meson_version: '>= 0.56.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
@@ -73,6 +73,9 @@ test_common_flags = [
|
|||||||
# clang specific warning options:
|
# clang specific warning options:
|
||||||
'-Wunreachable-code-aggressive',
|
'-Wunreachable-code-aggressive',
|
||||||
'-Wused-but-marked-unused',
|
'-Wused-but-marked-unused',
|
||||||
|
|
||||||
|
# suppress bogus GCC12 warnings in libfmt headers
|
||||||
|
'-Wno-stringop-overflow',
|
||||||
]
|
]
|
||||||
|
|
||||||
test_global_cxxflags = test_global_common_flags + [
|
test_global_cxxflags = test_global_common_flags + [
|
||||||
@@ -349,7 +352,7 @@ sources = [
|
|||||||
'src/TagStream.cxx',
|
'src/TagStream.cxx',
|
||||||
'src/TagAny.cxx',
|
'src/TagAny.cxx',
|
||||||
'src/TimePrint.cxx',
|
'src/TimePrint.cxx',
|
||||||
'src/mixer/Volume.cxx',
|
'src/mixer/Memento.cxx',
|
||||||
'src/PlaylistFile.cxx',
|
'src/PlaylistFile.cxx',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ flac = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
zlib = ZlibProject(
|
zlib = ZlibProject(
|
||||||
'http://zlib.net/zlib-1.2.11.tar.xz',
|
'http://zlib.net/zlib-1.2.12.tar.xz',
|
||||||
'4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066',
|
'7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18',
|
||||||
'lib/libz.a',
|
'lib/libz.a',
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -151,8 +151,8 @@ gme = CmakeProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ffmpeg = FfmpegProject(
|
ffmpeg = FfmpegProject(
|
||||||
'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz',
|
'http://ffmpeg.org/releases/ffmpeg-5.1.tar.xz',
|
||||||
'51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298',
|
'55eb6aab5ee235550fa54a33eaf8bf1b4ec66c01453182b12f6a993d75698b03',
|
||||||
'lib/libavcodec.a',
|
'lib/libavcodec.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -166,7 +166,6 @@ ffmpeg = FfmpegProject(
|
|||||||
'--disable-swscale',
|
'--disable-swscale',
|
||||||
'--disable-postproc',
|
'--disable-postproc',
|
||||||
'--disable-avfilter',
|
'--disable-avfilter',
|
||||||
'--disable-lzo',
|
|
||||||
'--disable-faan',
|
'--disable-faan',
|
||||||
'--disable-pixelutils',
|
'--disable-pixelutils',
|
||||||
'--disable-network',
|
'--disable-network',
|
||||||
@@ -177,6 +176,8 @@ ffmpeg = FfmpegProject(
|
|||||||
'--disable-filters',
|
'--disable-filters',
|
||||||
'--disable-v4l2_m2m',
|
'--disable-v4l2_m2m',
|
||||||
|
|
||||||
|
'--disable-vulkan',
|
||||||
|
|
||||||
'--disable-parser=bmp',
|
'--disable-parser=bmp',
|
||||||
'--disable-parser=cavsvideo',
|
'--disable-parser=cavsvideo',
|
||||||
'--disable-parser=dvbsub',
|
'--disable-parser=dvbsub',
|
||||||
@@ -380,14 +381,14 @@ ffmpeg = FfmpegProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
openssl = OpenSSLProject(
|
openssl = OpenSSLProject(
|
||||||
'https://www.openssl.org/source/openssl-3.0.1.tar.gz',
|
'https://www.openssl.org/source/openssl-3.0.5.tar.gz',
|
||||||
'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1',
|
'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a',
|
||||||
'include/openssl/ossl_typ.h',
|
'include/openssl/ossl_typ.h',
|
||||||
)
|
)
|
||||||
|
|
||||||
curl = CmakeProject(
|
curl = CmakeProject(
|
||||||
'https://curl.se/download/curl-7.82.0.tar.xz',
|
'https://curl.se/download/curl-7.84.0.tar.xz',
|
||||||
'0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c',
|
'2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'-DBUILD_CURL_EXE=OFF',
|
'-DBUILD_CURL_EXE=OFF',
|
||||||
@@ -444,7 +445,7 @@ jack = JackProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
boost = BoostProject(
|
boost = BoostProject(
|
||||||
'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2',
|
'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2',
|
||||||
'8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc',
|
'475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39',
|
||||||
'include/boost/version.hpp',
|
'include/boost/version.hpp',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ void
|
|||||||
LogFmt(LogLevel level, const Domain &domain,
|
LogFmt(LogLevel level, const Domain &domain,
|
||||||
const S &format_str, Args&&... args) noexcept
|
const S &format_str, Args&&... args) noexcept
|
||||||
{
|
{
|
||||||
#if FMT_VERSION >= 70000
|
#if FMT_VERSION >= 90000
|
||||||
|
return LogVFmt(level, domain, format_str,
|
||||||
|
fmt::make_format_args(args...));
|
||||||
|
#elif FMT_VERSION >= 70000
|
||||||
return LogVFmt(level, domain, fmt::to_string_view(format_str),
|
return LogVFmt(level, domain, fmt::to_string_view(format_str),
|
||||||
fmt::make_args_checked<Args...>(format_str,
|
fmt::make_args_checked<Args...>(format_str,
|
||||||
args...));
|
args...));
|
||||||
|
|||||||
48
src/Main.cxx
48
src/Main.cxx
@@ -590,19 +590,46 @@ MainConfigured(const CommandLineOptions &options,
|
|||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for ReadConfigFile() which returns false if the file was
|
||||||
|
* not found.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
TryReadConfigFile(ConfigData &config, Path path)
|
||||||
|
{
|
||||||
|
if (!FileExists(path))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ReadConfigFile(config, path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
AndroidMain()
|
LoadConfigFile(JNIEnv *env, ConfigData &config)
|
||||||
|
{
|
||||||
|
/* try loading mpd.conf from
|
||||||
|
"Android/data/org.musicpd/files/mpd.conf" (the app specific
|
||||||
|
data directory) first */
|
||||||
|
if (const auto dir = context->GetExternalFilesDir(env);
|
||||||
|
!dir.IsNull() &&
|
||||||
|
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* if that fails, attempt to load "mpd.conf" from the root of
|
||||||
|
the SD card (pre-0.23.9, ceases to work since Android
|
||||||
|
12) */
|
||||||
|
if (const auto dir = Environment::getExternalStorageDirectory(env);
|
||||||
|
!dir.IsNull())
|
||||||
|
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
AndroidMain(JNIEnv *env)
|
||||||
{
|
{
|
||||||
CommandLineOptions options;
|
CommandLineOptions options;
|
||||||
ConfigData raw_config;
|
ConfigData raw_config;
|
||||||
|
|
||||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
LoadConfigFile(env, raw_config);
|
||||||
if (!sdcard.IsNull()) {
|
|
||||||
const auto config_path =
|
|
||||||
sdcard / Path::FromFS("mpd.conf");
|
|
||||||
if (FileExists(config_path))
|
|
||||||
ReadConfigFile(raw_config, config_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
MainConfigured(options, raw_config);
|
MainConfigured(options, raw_config);
|
||||||
}
|
}
|
||||||
@@ -614,9 +641,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
|||||||
Java::Init(env);
|
Java::Init(env);
|
||||||
Java::Object::Initialise(env);
|
Java::Object::Initialise(env);
|
||||||
Java::File::Initialise(env);
|
Java::File::Initialise(env);
|
||||||
|
|
||||||
Environment::Initialise(env);
|
Environment::Initialise(env);
|
||||||
AtScopeExit(env) { Environment::Deinitialise(env); };
|
AtScopeExit(env) { Environment::Deinitialise(env); };
|
||||||
|
|
||||||
|
Context::Initialise(env);
|
||||||
|
|
||||||
context = new Context(env, _context);
|
context = new Context(env, _context);
|
||||||
AtScopeExit() { delete context; };
|
AtScopeExit() { delete context; };
|
||||||
|
|
||||||
@@ -625,7 +655,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
|||||||
AtScopeExit() { delete logListener; };
|
AtScopeExit() { delete logListener; };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AndroidMain();
|
AndroidMain(env);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
LogError(std::current_exception());
|
LogError(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
#include "mixer/Volume.hxx"
|
|
||||||
#include "IdleFlags.hxx"
|
#include "IdleFlags.hxx"
|
||||||
#include "client/Listener.hxx"
|
#include "client/Listener.hxx"
|
||||||
#include "client/Client.hxx"
|
#include "client/Client.hxx"
|
||||||
@@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
|
|||||||
void
|
void
|
||||||
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
|
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
|
||||||
{
|
{
|
||||||
InvalidateHardwareVolume();
|
mixer_memento.InvalidateHardwareVolume();
|
||||||
|
|
||||||
/* notify clients */
|
/* notify clients */
|
||||||
EmitIdle(IDLE_MIXER);
|
EmitIdle(IDLE_MIXER);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "queue/Listener.hxx"
|
#include "queue/Listener.hxx"
|
||||||
#include "output/MultipleOutputs.hxx"
|
#include "output/MultipleOutputs.hxx"
|
||||||
#include "mixer/Listener.hxx"
|
#include "mixer/Listener.hxx"
|
||||||
|
#include "mixer/Memento.hxx"
|
||||||
#include "player/Control.hxx"
|
#include "player/Control.hxx"
|
||||||
#include "player/Listener.hxx"
|
#include "player/Listener.hxx"
|
||||||
#include "protocol/RangeArg.hxx"
|
#include "protocol/RangeArg.hxx"
|
||||||
@@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
|||||||
|
|
||||||
MultipleOutputs outputs;
|
MultipleOutputs outputs;
|
||||||
|
|
||||||
|
MixerMemento mixer_memento;
|
||||||
|
|
||||||
PlayerControl pc;
|
PlayerControl pc;
|
||||||
|
|
||||||
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
|
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
#include "storage/StorageState.hxx"
|
#include "storage/StorageState.hxx"
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
#include "mixer/Volume.hxx"
|
|
||||||
#include "SongLoader.hxx"
|
#include "SongLoader.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
|
|||||||
void
|
void
|
||||||
StateFile::RememberVersions() noexcept
|
StateFile::RememberVersions() noexcept
|
||||||
{
|
{
|
||||||
prev_volume_version = sw_volume_state_get_hash();
|
prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash();
|
||||||
prev_output_version = audio_output_state_get_version();
|
prev_output_version = audio_output_state_get_version();
|
||||||
prev_playlist_version = playlist_state_get_hash(partition.playlist,
|
prev_playlist_version = playlist_state_get_hash(partition.playlist,
|
||||||
partition.pc);
|
partition.pc);
|
||||||
@@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
|
|||||||
bool
|
bool
|
||||||
StateFile::IsModified() const noexcept
|
StateFile::IsModified() const noexcept
|
||||||
{
|
{
|
||||||
return prev_volume_version != sw_volume_state_get_hash() ||
|
return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() ||
|
||||||
prev_output_version != audio_output_state_get_version() ||
|
prev_output_version != audio_output_state_get_version() ||
|
||||||
prev_playlist_version != playlist_state_get_hash(partition.playlist,
|
prev_playlist_version != playlist_state_get_hash(partition.playlist,
|
||||||
partition.pc)
|
partition.pc)
|
||||||
@@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
|
|||||||
inline void
|
inline void
|
||||||
StateFile::Write(BufferedOutputStream &os)
|
StateFile::Write(BufferedOutputStream &os)
|
||||||
{
|
{
|
||||||
save_sw_volume_state(os);
|
partition.mixer_memento.SaveSoftwareVolumeState(os);
|
||||||
audio_output_state_save(os, partition.outputs);
|
audio_output_state_save(os, partition.outputs);
|
||||||
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
@@ -125,7 +124,7 @@ try {
|
|||||||
|
|
||||||
const char *line;
|
const char *line;
|
||||||
while ((line = file.ReadLine()) != nullptr) {
|
while ((line = file.ReadLine()) != nullptr) {
|
||||||
success = read_sw_volume_state(line, partition.outputs) ||
|
success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) ||
|
||||||
audio_output_state_read(line, partition.outputs) ||
|
audio_output_state_read(line, partition.outputs) ||
|
||||||
playlist_state_restore(config, line, file, song_loader,
|
playlist_state_restore(config, line, file, song_loader,
|
||||||
partition.playlist,
|
partition.playlist,
|
||||||
|
|||||||
@@ -26,19 +26,30 @@
|
|||||||
|
|
||||||
#include "AudioManager.hxx"
|
#include "AudioManager.hxx"
|
||||||
|
|
||||||
|
static jmethodID getExternalFilesDir_method,
|
||||||
|
getCacheDir_method,
|
||||||
|
getSystemService_method;
|
||||||
|
|
||||||
|
void
|
||||||
|
Context::Initialise(JNIEnv *env) noexcept
|
||||||
|
{
|
||||||
|
Java::Class cls{env, "android/content/Context"};
|
||||||
|
|
||||||
|
getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||||
|
"(Ljava/lang/String;)Ljava/io/File;");
|
||||||
|
getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
|
||||||
|
"()Ljava/io/File;");
|
||||||
|
getSystemService_method = env->GetMethodID(cls, "getSystemService",
|
||||||
|
"(Ljava/lang/String;)Ljava/lang/Object;");
|
||||||
|
}
|
||||||
|
|
||||||
AllocatedPath
|
AllocatedPath
|
||||||
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
|
Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept
|
||||||
{
|
{
|
||||||
assert(_type != nullptr);
|
assert(_type != nullptr);
|
||||||
|
|
||||||
Java::Class cls{env, env->GetObjectClass(Get())};
|
jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method,
|
||||||
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
|
Java::String::Optional(env, type).Get());
|
||||||
"(Ljava/lang/String;)Ljava/io/File;");
|
|
||||||
assert(method);
|
|
||||||
|
|
||||||
Java::String type{env, _type};
|
|
||||||
|
|
||||||
jobject file = env->CallObjectMethod(Get(), method, type.Get());
|
|
||||||
if (Java::DiscardException(env) || file == nullptr)
|
if (Java::DiscardException(env) || file == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
|
|||||||
{
|
{
|
||||||
assert(env != nullptr);
|
assert(env != nullptr);
|
||||||
|
|
||||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
|
||||||
jmethodID method = env->GetMethodID(cls, "getCacheDir",
|
|
||||||
"()Ljava/io/File;");
|
|
||||||
assert(method);
|
|
||||||
|
|
||||||
jobject file = env->CallObjectMethod(Get(), method);
|
|
||||||
if (Java::DiscardException(env) || file == nullptr)
|
if (Java::DiscardException(env) || file == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
|
|||||||
{
|
{
|
||||||
assert(env != nullptr);
|
assert(env != nullptr);
|
||||||
|
|
||||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
|
||||||
jmethodID method = env->GetMethodID(cls, "getSystemService",
|
|
||||||
"(Ljava/lang/String;)Ljava/lang/Object;");
|
|
||||||
assert(method);
|
|
||||||
|
|
||||||
Java::String name(env, "audio");
|
Java::String name(env, "audio");
|
||||||
jobject am = env->CallObjectMethod(Get(), method, name.Get());
|
jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
|
||||||
if (Java::DiscardException(env) || am == nullptr)
|
if (Java::DiscardException(env) || am == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,21 @@ class AudioManager;
|
|||||||
|
|
||||||
class Context : public Java::GlobalObject {
|
class Context : public Java::GlobalObject {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Global initialisation. Looks up the methods of the
|
||||||
|
* Context Java class.
|
||||||
|
*/
|
||||||
|
static void Initialise(JNIEnv *env) noexcept;
|
||||||
|
|
||||||
Context(JNIEnv *env, jobject obj) noexcept
|
Context(JNIEnv *env, jobject obj) noexcept
|
||||||
:Java::GlobalObject(env, obj) {}
|
:Java::GlobalObject(env, obj) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type the subdirectory name; may be nullptr
|
||||||
|
*/
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
AllocatedPath GetExternalFilesDir(JNIEnv *env,
|
AllocatedPath GetExternalFilesDir(JNIEnv *env,
|
||||||
const char *type) noexcept;
|
const char *type=nullptr) noexcept;
|
||||||
|
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
||||||
|
|||||||
@@ -25,13 +25,13 @@
|
|||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
|
||||||
namespace Environment {
|
namespace Environment {
|
||||||
|
|
||||||
static Java::TrivialClass cls;
|
static Java::TrivialClass cls;
|
||||||
static jmethodID getExternalStorageDirectory_method;
|
static jmethodID getExternalStorageDirectory_method;
|
||||||
static jmethodID getExternalStoragePublicDirectory_method;
|
static jmethodID getExternalStoragePublicDirectory_method;
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Environment::Initialise(JNIEnv *env) noexcept
|
Initialise(JNIEnv *env) noexcept
|
||||||
{
|
{
|
||||||
cls.Find(env, "android/os/Environment");
|
cls.Find(env, "android/os/Environment");
|
||||||
|
|
||||||
@@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Environment::Deinitialise(JNIEnv *env) noexcept
|
Deinitialise(JNIEnv *env) noexcept
|
||||||
{
|
{
|
||||||
cls.Clear(env);
|
cls.Clear(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
AllocatedPath
|
AllocatedPath
|
||||||
Environment::getExternalStorageDirectory() noexcept
|
getExternalStorageDirectory(JNIEnv *env) noexcept
|
||||||
{
|
{
|
||||||
JNIEnv *env = Java::GetEnv();
|
|
||||||
|
|
||||||
jobject file =
|
jobject file =
|
||||||
env->CallStaticObjectMethod(cls,
|
env->CallStaticObjectMethod(cls,
|
||||||
getExternalStorageDirectory_method);
|
getExternalStorageDirectory_method);
|
||||||
@@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
AllocatedPath
|
AllocatedPath
|
||||||
Environment::getExternalStoragePublicDirectory(const char *type) noexcept
|
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
|
||||||
{
|
{
|
||||||
if (getExternalStoragePublicDirectory_method == nullptr)
|
if (getExternalStoragePublicDirectory_method == nullptr)
|
||||||
/* needs API level 8 */
|
/* needs API level 8 */
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
JNIEnv *env = Java::GetEnv();
|
|
||||||
|
|
||||||
Java::String type2(env, type);
|
Java::String type2(env, type);
|
||||||
jobject file = env->CallStaticObjectMethod(Environment::cls,
|
jobject file = env->CallStaticObjectMethod(cls,
|
||||||
Environment::getExternalStoragePublicDirectory_method,
|
getExternalStoragePublicDirectory_method,
|
||||||
type2.Get());
|
type2.Get());
|
||||||
if (file == nullptr)
|
if (file == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return Java::File::ToAbsolutePath(env, file);
|
return Java::File::ToAbsolutePath(env, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Environment
|
||||||
|
|||||||
@@ -17,27 +17,29 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_ANDROID_ENVIRONMENT_HXX
|
#pragma once
|
||||||
#define MPD_ANDROID_ENVIRONMENT_HXX
|
|
||||||
|
|
||||||
#include "util/Compiler.h"
|
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
class AllocatedPath;
|
class AllocatedPath;
|
||||||
|
|
||||||
namespace Environment {
|
namespace Environment {
|
||||||
void Initialise(JNIEnv *env) noexcept;
|
|
||||||
void Deinitialise(JNIEnv *env) noexcept;
|
void
|
||||||
|
Initialise(JNIEnv *env) noexcept;
|
||||||
|
|
||||||
|
void
|
||||||
|
Deinitialise(JNIEnv *env) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the mount point of the external SD card.
|
* Determine the mount point of the external SD card.
|
||||||
*/
|
*/
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
AllocatedPath getExternalStorageDirectory() noexcept;
|
AllocatedPath
|
||||||
|
getExternalStorageDirectory(JNIEnv *env) noexcept;
|
||||||
|
|
||||||
[[gnu::pure]]
|
[[gnu::pure]]
|
||||||
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
|
AllocatedPath
|
||||||
}
|
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
|
||||||
|
|
||||||
#endif
|
} // namespace Environment
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "ErrorRef.hxx"
|
#include "ErrorRef.hxx"
|
||||||
#include "StringRef.hxx"
|
#include "StringRef.hxx"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace Apple {
|
namespace Apple {
|
||||||
@@ -57,8 +58,8 @@ ThrowOSStatus(OSStatus status, const char *_msg)
|
|||||||
const Apple::StringRef cfstr(cferr.CopyDescription());
|
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||||
|
|
||||||
char msg[1024];
|
char msg[1024];
|
||||||
strcpy(msg, _msg);
|
std::strcpy(msg, _msg);
|
||||||
size_t length = strlen(msg);
|
size_t length = std::strlen(msg);
|
||||||
|
|
||||||
cfstr.GetCString(msg + length, sizeof(msg) - length);
|
cfstr.GetCString(msg + length, sizeof(msg) - length);
|
||||||
throw std::runtime_error(msg);
|
throw std::runtime_error(msg);
|
||||||
|
|||||||
@@ -82,7 +82,10 @@ public:
|
|||||||
|
|
||||||
template<typename S, typename... Args>
|
template<typename S, typename... Args>
|
||||||
bool Fmt(const S &format_str, Args&&... args) noexcept {
|
bool Fmt(const S &format_str, Args&&... args) noexcept {
|
||||||
#if FMT_VERSION >= 70000
|
#if FMT_VERSION >= 90000
|
||||||
|
return VFmt(format_str,
|
||||||
|
fmt::make_format_args(args...));
|
||||||
|
#elif FMT_VERSION >= 70000
|
||||||
return VFmt(fmt::to_string_view(format_str),
|
return VFmt(fmt::to_string_view(format_str),
|
||||||
fmt::make_args_checked<Args...>(format_str,
|
fmt::make_args_checked<Args...>(format_str,
|
||||||
args...));
|
args...));
|
||||||
@@ -109,7 +112,10 @@ public:
|
|||||||
template<typename S, typename... Args>
|
template<typename S, typename... Args>
|
||||||
void FmtError(enum ack code,
|
void FmtError(enum ack code,
|
||||||
const S &format_str, Args&&... args) noexcept {
|
const S &format_str, Args&&... args) noexcept {
|
||||||
#if FMT_VERSION >= 70000
|
#if FMT_VERSION >= 90000
|
||||||
|
return VFmtError(code, format_str,
|
||||||
|
fmt::make_format_args(args...));
|
||||||
|
#elif FMT_VERSION >= 70000
|
||||||
return VFmtError(code, fmt::to_string_view(format_str),
|
return VFmtError(code, fmt::to_string_view(format_str),
|
||||||
fmt::make_args_checked<Args...>(format_str,
|
fmt::make_args_checked<Args...>(format_str,
|
||||||
args...));
|
args...));
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
#include "TimePrint.hxx"
|
#include "TimePrint.hxx"
|
||||||
#include "decoder/DecoderPrint.hxx"
|
#include "decoder/DecoderPrint.hxx"
|
||||||
#include "ls.hxx"
|
#include "ls.hxx"
|
||||||
#include "mixer/Volume.hxx"
|
|
||||||
#include "time/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/StringAPI.hxx"
|
#include "util/StringAPI.hxx"
|
||||||
@@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
|
|||||||
{
|
{
|
||||||
auto &partition = client.GetPartition();
|
auto &partition = client.GetPartition();
|
||||||
|
|
||||||
const auto volume = volume_level_get(partition.outputs);
|
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
|
||||||
if (volume >= 0)
|
if (volume >= 0)
|
||||||
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
||||||
|
|
||||||
@@ -333,15 +332,13 @@ handle_getvol(Client &client, Request, Response &r)
|
|||||||
}
|
}
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
handle_setvol(Client &client, Request args, Response &r)
|
handle_setvol(Client &client, Request args, Response &)
|
||||||
{
|
{
|
||||||
unsigned level = args.ParseUnsigned(0, 100);
|
unsigned level = args.ParseUnsigned(0, 100);
|
||||||
|
|
||||||
if (!volume_level_change(client.GetPartition().outputs, level)) {
|
auto &partition = client.GetPartition();
|
||||||
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
|
partition.mixer_memento.SetVolume(partition.outputs, level);
|
||||||
return CommandResult::ERROR;
|
partition.EmitIdle(IDLE_MIXER);
|
||||||
}
|
|
||||||
|
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
|
|||||||
{
|
{
|
||||||
int relative = args.ParseInt(0, -100, 100);
|
int relative = args.ParseInt(0, -100, 100);
|
||||||
|
|
||||||
auto &outputs = client.GetPartition().outputs;
|
auto &partition = client.GetPartition();
|
||||||
|
auto &outputs = partition.outputs;
|
||||||
|
auto &mixer_memento = partition.mixer_memento;
|
||||||
|
|
||||||
const int old_volume = volume_level_get(outputs);
|
const int old_volume = mixer_memento.GetVolume(outputs);
|
||||||
if (old_volume < 0) {
|
if (old_volume < 0) {
|
||||||
r.Error(ACK_ERROR_SYSTEM, "No mixer");
|
r.Error(ACK_ERROR_SYSTEM, "No mixer");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
@@ -364,10 +363,9 @@ handle_volume(Client &client, Request args, Response &r)
|
|||||||
else if (new_volume > 100)
|
else if (new_volume > 100)
|
||||||
new_volume = 100;
|
new_volume = 100;
|
||||||
|
|
||||||
if (new_volume != old_volume &&
|
if (new_volume != old_volume) {
|
||||||
!volume_level_change(outputs, new_volume)) {
|
mixer_memento.SetVolume(outputs, new_volume);
|
||||||
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
|
partition.EmitIdle(IDLE_MIXER);
|
||||||
return CommandResult::ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
|
|||||||
assert(args.size == 1);
|
assert(args.size == 1);
|
||||||
unsigned device = args.ParseUnsigned(0);
|
unsigned device = args.ParseUnsigned(0);
|
||||||
|
|
||||||
if (!audio_output_enable_index(client.GetPartition().outputs, device)) {
|
auto &partition = client.GetPartition();
|
||||||
|
|
||||||
|
if (!audio_output_enable_index(partition.outputs,
|
||||||
|
partition.mixer_memento,
|
||||||
|
device)) {
|
||||||
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
@@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
|
|||||||
assert(args.size == 1);
|
assert(args.size == 1);
|
||||||
unsigned device = args.ParseUnsigned(0);
|
unsigned device = args.ParseUnsigned(0);
|
||||||
|
|
||||||
if (!audio_output_disable_index(client.GetPartition().outputs, device)) {
|
auto &partition = client.GetPartition();
|
||||||
|
|
||||||
|
if (!audio_output_disable_index(partition.outputs,
|
||||||
|
partition.mixer_memento,
|
||||||
|
device)) {
|
||||||
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
@@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
|
|||||||
assert(args.size == 1);
|
assert(args.size == 1);
|
||||||
unsigned device = args.ParseUnsigned(0);
|
unsigned device = args.ParseUnsigned(0);
|
||||||
|
|
||||||
if (!audio_output_toggle_index(client.GetPartition().outputs, device)) {
|
auto &partition = client.GetPartition();
|
||||||
|
|
||||||
|
if (!audio_output_toggle_index(partition.outputs,
|
||||||
|
partition.mixer_memento,
|
||||||
|
device)) {
|
||||||
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
#include "SingleMode.hxx"
|
#include "SingleMode.hxx"
|
||||||
#include "client/Client.hxx"
|
#include "client/Client.hxx"
|
||||||
#include "client/Response.hxx"
|
#include "client/Response.hxx"
|
||||||
#include "mixer/Volume.hxx"
|
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
#include "IdleFlags.hxx"
|
#include "IdleFlags.hxx"
|
||||||
@@ -131,7 +130,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
|||||||
|
|
||||||
const auto &playlist = partition.playlist;
|
const auto &playlist = partition.playlist;
|
||||||
|
|
||||||
const auto volume = volume_level_get(partition.outputs);
|
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
|
||||||
if (volume >= 0)
|
if (volume >= 0)
|
||||||
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
||||||
|
|
||||||
|
|||||||
@@ -257,6 +257,12 @@ public:
|
|||||||
return HasFailed();
|
return HasFailed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[gnu::pure]]
|
||||||
|
bool LockIsReplayGainEnabled() const noexcept {
|
||||||
|
const std::scoped_lock<Mutex> protect(mutex);
|
||||||
|
return replay_gain_mode != ReplayGainMode::OFF;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transition this obejct from DecoderState::START to
|
* Transition this obejct from DecoderState::START to
|
||||||
* DecoderState::DECODE.
|
* DecoderState::DECODE.
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
|
#include "util/StringCompare.hxx"
|
||||||
#include "thread/Name.hxx"
|
#include "thread/Name.hxx"
|
||||||
#include "tag/ApeReplayGain.hxx"
|
#include "tag/ApeReplayGain.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -261,12 +262,16 @@ LoadReplayGain(DecoderClient &client, InputStream &is)
|
|||||||
static void
|
static void
|
||||||
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
|
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
|
||||||
{
|
{
|
||||||
{
|
if (!bridge.dc.LockIsReplayGainEnabled())
|
||||||
const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
|
|
||||||
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
|
|
||||||
/* ReplayGain is disabled */
|
/* ReplayGain is disabled */
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
if (is.HasMimeType() &&
|
||||||
|
StringStartsWith(is.GetMimeType(), "audio/x-mpd-"))
|
||||||
|
/* skip for (virtual) files (e.g. from the
|
||||||
|
cdio_paranoia input plugin) which cannot possibly
|
||||||
|
contain tags */
|
||||||
|
return;
|
||||||
|
|
||||||
LoadReplayGain(bridge, is);
|
LoadReplayGain(bridge, is);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ static void
|
|||||||
FfmpegParseMetaData(const AVStream &stream,
|
FfmpegParseMetaData(const AVStream &stream,
|
||||||
ReplayGainInfo &rg, MixRampInfo &mr)
|
ReplayGainInfo &rg, MixRampInfo &mr)
|
||||||
{
|
{
|
||||||
|
if (stream.metadata != nullptr)
|
||||||
FfmpegParseMetaData(*stream.metadata, rg, mr);
|
FfmpegParseMetaData(*stream.metadata, rg, mr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +394,9 @@ FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream,
|
|||||||
{
|
{
|
||||||
assert(audio_stream >= 0);
|
assert(audio_stream >= 0);
|
||||||
|
|
||||||
|
if (format_context.metadata != nullptr)
|
||||||
FfmpegParseMetaData(*format_context.metadata, rg, mr);
|
FfmpegParseMetaData(*format_context.metadata, rg, mr);
|
||||||
|
|
||||||
FfmpegParseMetaData(*format_context.streams[audio_stream],
|
FfmpegParseMetaData(*format_context.streams[audio_stream],
|
||||||
rg, mr);
|
rg, mr);
|
||||||
}
|
}
|
||||||
@@ -468,7 +471,7 @@ static bool
|
|||||||
IsSeekable(const AVFormatContext &format_context) noexcept
|
IsSeekable(const AVFormatContext &format_context) noexcept
|
||||||
{
|
{
|
||||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100)
|
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100)
|
||||||
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0;
|
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) == 0;
|
||||||
#else
|
#else
|
||||||
(void)format_context;
|
(void)format_context;
|
||||||
return false;
|
return false;
|
||||||
@@ -520,9 +523,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
const unsigned channels = codec_context->ch_layout.nb_channels;
|
||||||
|
#else
|
||||||
|
const unsigned channels = codec_context->channels;
|
||||||
|
#endif
|
||||||
|
|
||||||
const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
|
const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
|
||||||
sample_format,
|
sample_format,
|
||||||
codec_context->channels);
|
channels);
|
||||||
|
|
||||||
const SignedSongTime total_time =
|
const SignedSongTime total_time =
|
||||||
av_stream.duration != (int64_t)AV_NOPTS_VALUE
|
av_stream.duration != (int64_t)AV_NOPTS_VALUE
|
||||||
@@ -530,9 +539,8 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
|||||||
: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
|
: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
|
||||||
|
|
||||||
client.Ready(audio_format,
|
client.Ready(audio_format,
|
||||||
input
|
(input ? input->IsSeekable() : false)
|
||||||
? input->IsSeekable()
|
|| IsSeekable(format_context),
|
||||||
: IsSeekable(format_context),
|
|
||||||
total_time);
|
total_time);
|
||||||
|
|
||||||
FfmpegParseMetaData(client, format_context, audio_stream);
|
FfmpegParseMetaData(client, format_context, audio_stream);
|
||||||
@@ -633,10 +641,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
|||||||
AV_TIME_BASE_Q));
|
AV_TIME_BASE_Q));
|
||||||
|
|
||||||
const auto &codec_params = *stream.codecpar;
|
const auto &codec_params = *stream.codecpar;
|
||||||
|
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
const unsigned channels = codec_params.ch_layout.nb_channels;
|
||||||
|
#else
|
||||||
|
const unsigned channels = codec_params.channels;
|
||||||
|
#endif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
|
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
|
||||||
ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
|
ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
|
||||||
codec_params.channels));
|
channels));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,13 @@
|
|||||||
#define __STDC_CONSTANT_MACROS
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
|
||||||
#include "FfmpegIo.hxx"
|
#include "FfmpegIo.hxx"
|
||||||
#include "libavutil/mem.h"
|
|
||||||
#include "../DecoderAPI.hxx"
|
#include "../DecoderAPI.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/mem.h>
|
||||||
|
}
|
||||||
|
|
||||||
AvioStream::~AvioStream()
|
AvioStream::~AvioStream()
|
||||||
{
|
{
|
||||||
if (io != nullptr) {
|
if (io != nullptr) {
|
||||||
|
|||||||
@@ -3,6 +3,23 @@ encoder_features = configuration_data()
|
|||||||
encoder_features.set('ENABLE_ENCODER', need_encoder)
|
encoder_features.set('ENABLE_ENCODER', need_encoder)
|
||||||
|
|
||||||
if not need_encoder
|
if not need_encoder
|
||||||
|
if need_wave_encoder
|
||||||
|
# Special case for the Snapcast output plugin which only needs the
|
||||||
|
# PCM wave encoder encoder plugin
|
||||||
|
encoder_glue = static_library(
|
||||||
|
'encoder_glue',
|
||||||
|
'plugins/WaveEncoderPlugin.cxx',
|
||||||
|
include_directories: inc,
|
||||||
|
)
|
||||||
|
|
||||||
|
encoder_glue_dep = declare_dependency(
|
||||||
|
link_with: encoder_glue,
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_file(output: 'Features.h', configuration: encoder_features)
|
||||||
|
subdir_done()
|
||||||
|
endif
|
||||||
|
|
||||||
encoder_glue_dep = dependency('', required: false)
|
encoder_glue_dep = dependency('', required: false)
|
||||||
configure_file(output: 'Features.h', configuration: encoder_features)
|
configure_file(output: 'Features.h', configuration: encoder_features)
|
||||||
subdir_done()
|
subdir_done()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ if libshine_dep.found()
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
|
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
|
||||||
if get_option('wave_encoder')
|
if get_option('wave_encoder') or need_wave_encoder
|
||||||
encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
|
encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
|
|||||||
buffer_sink(_buffer_sink),
|
buffer_sink(_buffer_sink),
|
||||||
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
|
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
|
||||||
in_sample_rate(in_audio_format.sample_rate),
|
in_sample_rate(in_audio_format.sample_rate),
|
||||||
|
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
|
||||||
in_channels(in_audio_format.channels),
|
in_channels(in_audio_format.channels),
|
||||||
|
#endif
|
||||||
in_audio_frame_size(in_audio_format.GetFrameSize()),
|
in_audio_frame_size(in_audio_format.GetFrameSize()),
|
||||||
out_audio_frame_size(_out_audio_format.GetFrameSize())
|
out_audio_frame_size(_out_audio_format.GetFrameSize())
|
||||||
{
|
{
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
@@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
|
|||||||
frame.Unref();
|
frame.Unref();
|
||||||
frame->format = in_format;
|
frame->format = in_format;
|
||||||
frame->sample_rate = in_sample_rate;
|
frame->sample_rate = in_sample_rate;
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
frame->ch_layout = in_ch_layout;
|
||||||
|
#else
|
||||||
frame->channels = in_channels;
|
frame->channels = in_channels;
|
||||||
|
#endif
|
||||||
frame->nb_samples = src.size / in_audio_frame_size;
|
frame->nb_samples = src.size / in_audio_frame_size;
|
||||||
|
|
||||||
frame.GetBuffer();
|
frame.GetBuffer();
|
||||||
|
|||||||
@@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
|
|||||||
|
|
||||||
FfmpegBuffer interleave_buffer;
|
FfmpegBuffer interleave_buffer;
|
||||||
|
|
||||||
const int in_format, in_sample_rate, in_channels;
|
const int in_format, in_sample_rate;
|
||||||
|
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
AVChannelLayout in_ch_layout;
|
||||||
|
#else
|
||||||
|
const int in_channels;
|
||||||
|
#endif
|
||||||
|
|
||||||
const size_t in_audio_frame_size;
|
const size_t in_audio_frame_size;
|
||||||
const size_t out_audio_frame_size;
|
const size_t out_audio_frame_size;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
#include "ReplayGainInfo.hxx"
|
#include "ReplayGainInfo.hxx"
|
||||||
#include "ReplayGainConfig.hxx"
|
#include "ReplayGainConfig.hxx"
|
||||||
#include "mixer/MixerControl.hxx"
|
#include "mixer/MixerControl.hxx"
|
||||||
|
#include "mixer/MixerInternal.hxx"
|
||||||
|
#include "mixer/Listener.hxx"
|
||||||
#include "pcm/AudioFormat.hxx"
|
#include "pcm/AudioFormat.hxx"
|
||||||
#include "pcm/Volume.hxx"
|
#include "pcm/Volume.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
@@ -171,9 +173,11 @@ ReplayGainFilter::Update()
|
|||||||
try {
|
try {
|
||||||
mixer_set_volume(mixer, _volume);
|
mixer_set_volume(mixer, _volume);
|
||||||
|
|
||||||
/* TODO: emit this idle event only for the
|
/* invoke the mixer's listener manually, just
|
||||||
current partition */
|
in case the mixer implementation didn't do
|
||||||
idle_add(IDLE_MIXER);
|
that already (this depends on the
|
||||||
|
implementation) */
|
||||||
|
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
LogError(std::current_exception(),
|
LogError(std::current_exception(),
|
||||||
"Failed to update hardware mixer");
|
"Failed to update hardware mixer");
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ GetUserMusicDir() noexcept
|
|||||||
#elif defined(USE_XDG)
|
#elif defined(USE_XDG)
|
||||||
return GetUserDir("XDG_MUSIC_DIR");
|
return GetUserDir("XDG_MUSIC_DIR");
|
||||||
#elif defined(ANDROID)
|
#elif defined(ANDROID)
|
||||||
return Environment::getExternalStoragePublicDirectory("Music");
|
return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
|
||||||
|
"Music");
|
||||||
#else
|
#else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -30,10 +30,12 @@
|
|||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
|
#include "util/ScopeExit.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "config/Block.hxx"
|
#include "config/Block.hxx"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
@@ -43,31 +45,35 @@
|
|||||||
|
|
||||||
#include <cdio/cd_types.h>
|
#include <cdio/cd_types.h>
|
||||||
|
|
||||||
|
static constexpr Domain cdio_domain("cdio");
|
||||||
|
|
||||||
|
static bool default_reverse_endian;
|
||||||
|
static unsigned speed = 0;
|
||||||
|
|
||||||
|
/* Default to full paranoia, but allow skipping sectors. */
|
||||||
|
static int mode_flags = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;
|
||||||
|
|
||||||
class CdioParanoiaInputStream final : public InputStream {
|
class CdioParanoiaInputStream final : public InputStream {
|
||||||
cdrom_drive_t *const drv;
|
cdrom_drive_t *const drv;
|
||||||
CdIo_t *const cdio;
|
CdIo_t *const cdio;
|
||||||
CdromParanoia para;
|
CdromParanoia para;
|
||||||
|
|
||||||
const lsn_t lsn_from, lsn_to;
|
const lsn_t lsn_from;
|
||||||
int lsn_relofs;
|
|
||||||
|
|
||||||
char buffer[CDIO_CD_FRAMESIZE_RAW];
|
char buffer[CDIO_CD_FRAMESIZE_RAW];
|
||||||
int buffer_lsn;
|
lsn_t buffer_lsn;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CdioParanoiaInputStream(const char *_uri, Mutex &_mutex,
|
CdioParanoiaInputStream(const char *_uri, Mutex &_mutex,
|
||||||
cdrom_drive_t *_drv, CdIo_t *_cdio,
|
cdrom_drive_t *_drv, CdIo_t *_cdio,
|
||||||
bool reverse_endian,
|
bool reverse_endian,
|
||||||
lsn_t _lsn_from, lsn_t _lsn_to)
|
lsn_t _lsn_from, lsn_t lsn_to)
|
||||||
:InputStream(_uri, _mutex),
|
:InputStream(_uri, _mutex),
|
||||||
drv(_drv), cdio(_cdio), para(drv),
|
drv(_drv), cdio(_cdio), para(drv),
|
||||||
lsn_from(_lsn_from), lsn_to(_lsn_to),
|
lsn_from(_lsn_from),
|
||||||
lsn_relofs(0),
|
|
||||||
buffer_lsn(-1)
|
buffer_lsn(-1)
|
||||||
{
|
{
|
||||||
/* Set reading mode for full paranoia, but allow
|
para.SetMode(mode_flags);
|
||||||
skipping sectors. */
|
|
||||||
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
|
|
||||||
|
|
||||||
/* seek to beginning of the track */
|
/* seek to beginning of the track */
|
||||||
para.Seek(lsn_from);
|
para.Seek(lsn_from);
|
||||||
@@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
|
|||||||
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
|
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr Domain cdio_domain("cdio");
|
|
||||||
|
|
||||||
static bool default_reverse_endian;
|
|
||||||
static unsigned speed = 0;
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
input_cdio_init(EventLoop &, const ConfigBlock &block)
|
input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||||
{
|
{
|
||||||
@@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
|
|||||||
value);
|
value);
|
||||||
}
|
}
|
||||||
speed = block.GetBlockValue("speed",0U);
|
speed = block.GetBlockValue("speed",0U);
|
||||||
|
|
||||||
|
if (const auto *param = block.GetBlockParam("mode")) {
|
||||||
|
param->With([](const char *s){
|
||||||
|
if (StringIsEqual(s, "disable"))
|
||||||
|
mode_flags = PARANOIA_MODE_DISABLE;
|
||||||
|
else if (StringIsEqual(s, "overlap"))
|
||||||
|
mode_flags = PARANOIA_MODE_OVERLAP;
|
||||||
|
else if (StringIsEqual(s, "full"))
|
||||||
|
mode_flags = PARANOIA_MODE_FULL;
|
||||||
|
else
|
||||||
|
throw std::invalid_argument{"Invalid paranoia mode"};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto *param = block.GetBlockParam("skip")) {
|
||||||
|
if (param->GetBoolValue())
|
||||||
|
mode_flags &= ~PARANOIA_MODE_NEVERSKIP;
|
||||||
|
else
|
||||||
|
mode_flags |= PARANOIA_MODE_NEVERSKIP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CdioUri {
|
struct CdioUri {
|
||||||
@@ -173,9 +194,12 @@ cdio_detect_device()
|
|||||||
if (devices == nullptr)
|
if (devices == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
AllocatedPath path = AllocatedPath::FromFS(devices[0]);
|
AtScopeExit(devices) { cdio_free_device_list(devices); };
|
||||||
cdio_free_device_list(devices);
|
|
||||||
return path;
|
if (devices[0] == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return AllocatedPath::FromFS(devices[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static InputStreamPtr
|
static InputStreamPtr
|
||||||
@@ -271,29 +295,30 @@ CdioParanoiaInputStream::Seek(std::unique_lock<Mutex> &,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* calculate current LSN */
|
/* calculate current LSN */
|
||||||
lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
|
const lsn_t lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
|
||||||
offset = new_offset;
|
|
||||||
|
|
||||||
{
|
if (lsn_relofs != buffer_lsn) {
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
para.Seek(lsn_from + lsn_relofs);
|
para.Seek(lsn_from + lsn_relofs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset = new_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
|
CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
|
||||||
void *ptr, size_t length)
|
void *ptr, size_t length)
|
||||||
{
|
{
|
||||||
size_t nbytes = 0;
|
|
||||||
char *wptr = (char *) ptr;
|
|
||||||
|
|
||||||
while (length > 0) {
|
|
||||||
/* end of track ? */
|
/* end of track ? */
|
||||||
if (lsn_from + lsn_relofs > lsn_to)
|
if (IsEOF())
|
||||||
break;
|
return 0;
|
||||||
|
|
||||||
//current sector was changed ?
|
//current sector was changed ?
|
||||||
const int16_t *rbuf;
|
const int16_t *rbuf;
|
||||||
|
|
||||||
|
const lsn_t lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
|
||||||
|
const std::size_t diff = offset % CDIO_CD_FRAMESIZE_RAW;
|
||||||
|
|
||||||
if (lsn_relofs != buffer_lsn) {
|
if (lsn_relofs != buffer_lsn) {
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
@@ -318,26 +343,14 @@ CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
|
|||||||
rbuf = (const int16_t *)buffer;
|
rbuf = (const int16_t *)buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
//correct offset
|
|
||||||
const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
|
|
||||||
|
|
||||||
assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
|
|
||||||
|
|
||||||
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
|
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
|
||||||
const size_t len = (length < maxwrite? length : maxwrite);
|
const std::size_t nbytes = std::min(length, maxwrite);
|
||||||
|
|
||||||
//skip diff bytes from this lsn
|
//skip diff bytes from this lsn
|
||||||
memcpy(wptr, ((const char *)rbuf) + diff, len);
|
memcpy(ptr, ((const char *)rbuf) + diff, nbytes);
|
||||||
//update pointer
|
|
||||||
wptr += len;
|
|
||||||
nbytes += len;
|
|
||||||
|
|
||||||
//update offset
|
//update offset
|
||||||
offset += len;
|
offset += nbytes;
|
||||||
lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
|
|
||||||
//update length
|
|
||||||
length -= len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nbytes;
|
return nbytes;
|
||||||
}
|
}
|
||||||
@@ -345,7 +358,7 @@ CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
|
|||||||
bool
|
bool
|
||||||
CdioParanoiaInputStream::IsEOF() const noexcept
|
CdioParanoiaInputStream::IsEOF() const noexcept
|
||||||
{
|
{
|
||||||
return lsn_from + lsn_relofs > lsn_to;
|
return offset >= size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr const char *cdio_paranoia_prefixes[] = {
|
static constexpr const char *cdio_paranoia_prefixes[] = {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
|
|||||||
public:
|
public:
|
||||||
template<typename I>
|
template<typename I>
|
||||||
CurlInputStream(EventLoop &event_loop, const char *_url,
|
CurlInputStream(EventLoop &event_loop, const char *_url,
|
||||||
const std::multimap<std::string, std::string> &headers,
|
const Curl::Headers &headers,
|
||||||
I &&_icy,
|
I &&_icy,
|
||||||
Mutex &_mutex);
|
Mutex &_mutex);
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ public:
|
|||||||
CurlInputStream &operator=(const CurlInputStream &) = delete;
|
CurlInputStream &operator=(const CurlInputStream &) = delete;
|
||||||
|
|
||||||
static InputStreamPtr Open(const char *url,
|
static InputStreamPtr Open(const char *url,
|
||||||
const std::multimap<std::string, std::string> &headers,
|
const Curl::Headers &headers,
|
||||||
Mutex &mutex);
|
Mutex &mutex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -131,8 +131,7 @@ private:
|
|||||||
void SeekInternal(offset_type new_offset);
|
void SeekInternal(offset_type new_offset);
|
||||||
|
|
||||||
/* virtual methods from CurlResponseHandler */
|
/* virtual methods from CurlResponseHandler */
|
||||||
void OnHeaders(unsigned status,
|
void OnHeaders(unsigned status, Curl::Headers &&headers) override;
|
||||||
std::multimap<std::string, std::string> &&headers) override;
|
|
||||||
void OnData(ConstBuffer<void> data) override;
|
void OnData(ConstBuffer<void> data) override;
|
||||||
void OnEnd() override;
|
void OnEnd() override;
|
||||||
void OnError(std::exception_ptr e) noexcept override;
|
void OnError(std::exception_ptr e) noexcept override;
|
||||||
@@ -227,7 +226,7 @@ WithConvertedTagValue(const char *uri, const char *value, F &&f) noexcept
|
|||||||
|
|
||||||
void
|
void
|
||||||
CurlInputStream::OnHeaders(unsigned status,
|
CurlInputStream::OnHeaders(unsigned status,
|
||||||
std::multimap<std::string, std::string> &&headers)
|
Curl::Headers &&headers)
|
||||||
{
|
{
|
||||||
assert(GetEventLoop().IsInside());
|
assert(GetEventLoop().IsInside());
|
||||||
assert(!postponed_exception);
|
assert(!postponed_exception);
|
||||||
@@ -391,7 +390,7 @@ input_curl_finish() noexcept
|
|||||||
template<typename I>
|
template<typename I>
|
||||||
inline
|
inline
|
||||||
CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
|
CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
|
||||||
const std::multimap<std::string, std::string> &headers,
|
const Curl::Headers &headers,
|
||||||
I &&_icy,
|
I &&_icy,
|
||||||
Mutex &_mutex)
|
Mutex &_mutex)
|
||||||
:AsyncInputStream(event_loop, _url, _mutex,
|
:AsyncInputStream(event_loop, _url, _mutex,
|
||||||
@@ -491,7 +490,7 @@ CurlInputStream::DoSeek(offset_type new_offset)
|
|||||||
|
|
||||||
inline InputStreamPtr
|
inline InputStreamPtr
|
||||||
CurlInputStream::Open(const char *url,
|
CurlInputStream::Open(const char *url,
|
||||||
const std::multimap<std::string, std::string> &headers,
|
const Curl::Headers &headers,
|
||||||
Mutex &mutex)
|
Mutex &mutex)
|
||||||
{
|
{
|
||||||
auto icy = std::make_shared<IcyMetaDataParser>();
|
auto icy = std::make_shared<IcyMetaDataParser>();
|
||||||
@@ -510,8 +509,7 @@ CurlInputStream::Open(const char *url,
|
|||||||
}
|
}
|
||||||
|
|
||||||
InputStreamPtr
|
InputStreamPtr
|
||||||
OpenCurlInputStream(const char *uri,
|
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
|
||||||
const std::multimap<std::string, std::string> &headers,
|
|
||||||
Mutex &mutex)
|
Mutex &mutex)
|
||||||
{
|
{
|
||||||
return CurlInputStream::Open(uri, headers, mutex);
|
return CurlInputStream::Open(uri, headers, mutex);
|
||||||
|
|||||||
@@ -20,12 +20,10 @@
|
|||||||
#ifndef MPD_INPUT_CURL_HXX
|
#ifndef MPD_INPUT_CURL_HXX
|
||||||
#define MPD_INPUT_CURL_HXX
|
#define MPD_INPUT_CURL_HXX
|
||||||
|
|
||||||
|
#include "lib/curl/Headers.hxx"
|
||||||
#include "input/Ptr.hxx"
|
#include "input/Ptr.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
extern const struct InputPlugin input_plugin_curl;
|
extern const struct InputPlugin input_plugin_curl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,8 +34,7 @@ extern const struct InputPlugin input_plugin_curl;
|
|||||||
* Throws on error.
|
* Throws on error.
|
||||||
*/
|
*/
|
||||||
InputStreamPtr
|
InputStreamPtr
|
||||||
OpenCurlInputStream(const char *uri,
|
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
|
||||||
const std::multimap<std::string, std::string> &headers,
|
|
||||||
Mutex &mutex);
|
Mutex &mutex);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ QobuzClient::InvokeHandlers() noexcept
|
|||||||
|
|
||||||
std::string
|
std::string
|
||||||
QobuzClient::MakeUrl(const char *object, const char *method,
|
QobuzClient::MakeUrl(const char *object, const char *method,
|
||||||
const std::multimap<std::string, std::string> &query) const noexcept
|
const Curl::Headers &query) const noexcept
|
||||||
{
|
{
|
||||||
assert(!query.empty());
|
assert(!query.empty());
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ QobuzClient::MakeUrl(const char *object, const char *method,
|
|||||||
|
|
||||||
std::string
|
std::string
|
||||||
QobuzClient::MakeSignedUrl(const char *object, const char *method,
|
QobuzClient::MakeSignedUrl(const char *object, const char *method,
|
||||||
const std::multimap<std::string, std::string> &query) const noexcept
|
const Curl::Headers &query) const noexcept
|
||||||
{
|
{
|
||||||
assert(!query.empty());
|
assert(!query.empty());
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,12 @@
|
|||||||
#include "QobuzSession.hxx"
|
#include "QobuzSession.hxx"
|
||||||
#include "QobuzLoginRequest.hxx"
|
#include "QobuzLoginRequest.hxx"
|
||||||
#include "lib/curl/Init.hxx"
|
#include "lib/curl/Init.hxx"
|
||||||
|
#include "lib/curl/Headers.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
#include "event/DeferEvent.hxx"
|
#include "event/DeferEvent.hxx"
|
||||||
#include "util/IntrusiveList.hxx"
|
#include "util/IntrusiveList.hxx"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class QobuzSessionHandler
|
class QobuzSessionHandler
|
||||||
@@ -94,10 +94,10 @@ public:
|
|||||||
QobuzSession GetSession() const;
|
QobuzSession GetSession() const;
|
||||||
|
|
||||||
std::string MakeUrl(const char *object, const char *method,
|
std::string MakeUrl(const char *object, const char *method,
|
||||||
const std::multimap<std::string, std::string> &query) const noexcept;
|
const Curl::Headers &query) const noexcept;
|
||||||
|
|
||||||
std::string MakeSignedUrl(const char *object, const char *method,
|
std::string MakeSignedUrl(const char *object, const char *method,
|
||||||
const std::multimap<std::string, std::string> &query) const noexcept;
|
const Curl::Headers &query) const noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void StartLogin();
|
void StartLogin();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ static constexpr yajl_callbacks qobuz_error_parser_callbacks = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QobuzErrorParser::QobuzErrorParser(unsigned _status,
|
QobuzErrorParser::QobuzErrorParser(unsigned _status,
|
||||||
const std::multimap<std::string, std::string> &headers)
|
const Curl::Headers &headers)
|
||||||
:YajlResponseParser(&qobuz_error_parser_callbacks, nullptr, this),
|
:YajlResponseParser(&qobuz_error_parser_callbacks, nullptr, this),
|
||||||
status(_status)
|
status(_status)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,11 +20,9 @@
|
|||||||
#ifndef QOBUZ_ERROR_PARSER_HXX
|
#ifndef QOBUZ_ERROR_PARSER_HXX
|
||||||
#define QOBUZ_ERROR_PARSER_HXX
|
#define QOBUZ_ERROR_PARSER_HXX
|
||||||
|
|
||||||
|
#include "lib/curl/Headers.hxx"
|
||||||
#include "lib/yajl/ResponseParser.hxx"
|
#include "lib/yajl/ResponseParser.hxx"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
template<typename T> struct ConstBuffer;
|
template<typename T> struct ConstBuffer;
|
||||||
struct StringView;
|
struct StringView;
|
||||||
|
|
||||||
@@ -46,8 +44,7 @@ public:
|
|||||||
* May throw if there is a formal error in the response
|
* May throw if there is a formal error in the response
|
||||||
* headers.
|
* headers.
|
||||||
*/
|
*/
|
||||||
QobuzErrorParser(unsigned status,
|
QobuzErrorParser(unsigned status, const Curl::Headers &headers);
|
||||||
const std::multimap<std::string, std::string> &headers);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/* virtual methods from CurlResponseParser */
|
/* virtual methods from CurlResponseParser */
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ QobuzLoginRequest::ResponseParser::GetSession()
|
|||||||
return std::move(session);
|
return std::move(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::multimap<std::string, std::string>
|
static Curl::Headers
|
||||||
MakeLoginForm(const char *app_id,
|
MakeLoginForm(const char *app_id,
|
||||||
const char *username, const char *email,
|
const char *username, const char *email,
|
||||||
const char *password,
|
const char *password,
|
||||||
@@ -85,7 +85,7 @@ MakeLoginForm(const char *app_id,
|
|||||||
{
|
{
|
||||||
assert(username != nullptr || email != nullptr);
|
assert(username != nullptr || email != nullptr);
|
||||||
|
|
||||||
std::multimap<std::string, std::string> form{
|
Curl::Headers form{
|
||||||
{"app_id", app_id},
|
{"app_id", app_id},
|
||||||
{"password", password},
|
{"password", password},
|
||||||
{"device_manufacturer_id", device_manufacturer_id},
|
{"device_manufacturer_id", device_manufacturer_id},
|
||||||
@@ -134,8 +134,7 @@ QobuzLoginRequest::~QobuzLoginRequest() noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<CurlResponseParser>
|
std::unique_ptr<CurlResponseParser>
|
||||||
QobuzLoginRequest::MakeParser(unsigned status,
|
QobuzLoginRequest::MakeParser(unsigned status, Curl::Headers &&headers)
|
||||||
std::multimap<std::string, std::string> &&headers)
|
|
||||||
{
|
{
|
||||||
if (status != 200)
|
if (status != 200)
|
||||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
/* virtual methods from DelegateCurlResponseHandler */
|
/* virtual methods from DelegateCurlResponseHandler */
|
||||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||||
std::multimap<std::string, std::string> &&headers) override;
|
Curl::Headers &&headers) override;
|
||||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||||
|
|
||||||
/* virtual methods from CurlResponseHandler */
|
/* virtual methods from CurlResponseHandler */
|
||||||
|
|||||||
@@ -99,8 +99,7 @@ QobuzTagScanner::~QobuzTagScanner() noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<CurlResponseParser>
|
std::unique_ptr<CurlResponseParser>
|
||||||
QobuzTagScanner::MakeParser(unsigned status,
|
QobuzTagScanner::MakeParser(unsigned status, Curl::Headers &&headers)
|
||||||
std::multimap<std::string, std::string> &&headers)
|
|
||||||
{
|
{
|
||||||
if (status != 200)
|
if (status != 200)
|
||||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
/* virtual methods from DelegateCurlResponseHandler */
|
/* virtual methods from DelegateCurlResponseHandler */
|
||||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||||
std::multimap<std::string, std::string> &&headers) override;
|
Curl::Headers &&headers) override;
|
||||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||||
|
|
||||||
/* virtual methods from CurlResponseHandler */
|
/* virtual methods from CurlResponseHandler */
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ QobuzTrackRequest::~QobuzTrackRequest() noexcept
|
|||||||
|
|
||||||
std::unique_ptr<CurlResponseParser>
|
std::unique_ptr<CurlResponseParser>
|
||||||
QobuzTrackRequest::MakeParser(unsigned status,
|
QobuzTrackRequest::MakeParser(unsigned status,
|
||||||
std::multimap<std::string, std::string> &&headers)
|
Curl::Headers &&headers)
|
||||||
{
|
{
|
||||||
if (status != 200)
|
if (status != 200)
|
||||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
/* virtual methods from DelegateCurlResponseHandler */
|
/* virtual methods from DelegateCurlResponseHandler */
|
||||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||||
std::multimap<std::string, std::string> &&headers) override;
|
Curl::Headers &&headers) override;
|
||||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||||
|
|
||||||
/* virtual methods from CurlResponseHandler */
|
/* virtual methods from CurlResponseHandler */
|
||||||
|
|||||||
@@ -89,6 +89,16 @@ public:
|
|||||||
String(JNIEnv *_env, const char *_value) noexcept
|
String(JNIEnv *_env, const char *_value) noexcept
|
||||||
:LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {}
|
:LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor allows passing a nullptr value, which maps
|
||||||
|
* to a "null" in Java.
|
||||||
|
*/
|
||||||
|
static String Optional(JNIEnv *_env, const char *_value) noexcept {
|
||||||
|
return _value != nullptr
|
||||||
|
? String{_env, _value}
|
||||||
|
: String{};
|
||||||
|
}
|
||||||
|
|
||||||
static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept {
|
static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept {
|
||||||
return {env, s, env->GetStringUTFChars(s, nullptr)};
|
return {env, s, env->GetStringUTFChars(s, nullptr)};
|
||||||
}
|
}
|
||||||
|
|||||||
204
src/lib/curl/Adapter.cxx
Normal file
204
src/lib/curl/Adapter.cxx
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008-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 "Adapter.hxx"
|
||||||
|
#include "Easy.hxx"
|
||||||
|
#include "Handler.hxx"
|
||||||
|
#include "util/CharUtil.hxx"
|
||||||
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/StringStrip.hxx"
|
||||||
|
#include "util/StringView.hxx"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
void
|
||||||
|
CurlResponseHandlerAdapter::Install(CurlEasy &easy)
|
||||||
|
{
|
||||||
|
assert(state == State::UNINITIALISED);
|
||||||
|
|
||||||
|
error_buffer[0] = 0;
|
||||||
|
easy.SetErrorBuffer(error_buffer);
|
||||||
|
|
||||||
|
easy.SetHeaderFunction(_HeaderFunction, this);
|
||||||
|
easy.SetWriteFunction(WriteFunction, this);
|
||||||
|
|
||||||
|
curl = easy.Get();
|
||||||
|
|
||||||
|
state = State::HEADERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CurlResponseHandlerAdapter::FinishHeaders()
|
||||||
|
{
|
||||||
|
assert(state >= State::HEADERS);
|
||||||
|
|
||||||
|
if (state != State::HEADERS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state = State::BODY;
|
||||||
|
|
||||||
|
long status = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||||
|
|
||||||
|
handler.OnHeaders(status, std::move(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CurlResponseHandlerAdapter::FinishBody()
|
||||||
|
{
|
||||||
|
FinishHeaders();
|
||||||
|
|
||||||
|
if (state != State::BODY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state = State::CLOSED;
|
||||||
|
handler.OnEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CurlResponseHandlerAdapter::Done(CURLcode result) noexcept
|
||||||
|
{
|
||||||
|
if (postponed_error) {
|
||||||
|
state = State::CLOSED;
|
||||||
|
handler.OnError(std::move(postponed_error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (result != CURLE_OK) {
|
||||||
|
StripRight(error_buffer);
|
||||||
|
const char *msg = error_buffer;
|
||||||
|
if (*msg == 0)
|
||||||
|
msg = curl_easy_strerror(result);
|
||||||
|
throw FormatRuntimeError("CURL failed: %s", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
FinishBody();
|
||||||
|
} catch (...) {
|
||||||
|
state = State::CLOSED;
|
||||||
|
handler.OnError(std::current_exception());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[gnu::pure]]
|
||||||
|
static bool
|
||||||
|
IsResponseBoundaryHeader(StringView s) noexcept
|
||||||
|
{
|
||||||
|
return s.size > 5 && (s.StartsWith("HTTP/") ||
|
||||||
|
/* the proprietary "ICY 200 OK" is
|
||||||
|
emitted by Shoutcast */
|
||||||
|
s.StartsWith("ICY 2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
CurlResponseHandlerAdapter::HeaderFunction(StringView s) noexcept
|
||||||
|
{
|
||||||
|
if (state > State::HEADERS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (IsResponseBoundaryHeader(s)) {
|
||||||
|
/* this is the boundary to a new response, for example
|
||||||
|
after a redirect */
|
||||||
|
headers.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *header = s.data;
|
||||||
|
const char *end = StripRight(header, header + s.size);
|
||||||
|
|
||||||
|
const char *value = s.Find(':');
|
||||||
|
if (value == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string name(header, value);
|
||||||
|
std::transform(name.begin(), name.end(), name.begin(),
|
||||||
|
static_cast<char(*)(char)>(ToLowerASCII));
|
||||||
|
|
||||||
|
/* skip the colon */
|
||||||
|
|
||||||
|
++value;
|
||||||
|
|
||||||
|
/* strip the value */
|
||||||
|
|
||||||
|
value = StripLeft(value, end);
|
||||||
|
end = StripRight(value, end);
|
||||||
|
|
||||||
|
headers.emplace(std::move(name), std::string(value, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t
|
||||||
|
CurlResponseHandlerAdapter::_HeaderFunction(char *ptr, std::size_t size,
|
||||||
|
std::size_t nmemb,
|
||||||
|
void *stream) noexcept
|
||||||
|
{
|
||||||
|
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
|
||||||
|
|
||||||
|
size *= nmemb;
|
||||||
|
|
||||||
|
c.HeaderFunction({ptr, size});
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::size_t
|
||||||
|
CurlResponseHandlerAdapter::DataReceived(const void *ptr,
|
||||||
|
std::size_t received_size) noexcept
|
||||||
|
{
|
||||||
|
assert(received_size > 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FinishHeaders();
|
||||||
|
handler.OnData({ptr, received_size});
|
||||||
|
return received_size;
|
||||||
|
} catch (CurlResponseHandler::Pause) {
|
||||||
|
return CURL_WRITEFUNC_PAUSE;
|
||||||
|
} catch (...) {
|
||||||
|
/* from inside this libCURL callback function, we
|
||||||
|
can't do much, so we remember the exception to be
|
||||||
|
handled later by Done(), and return 0, causing the
|
||||||
|
response to be aborted with CURLE_WRITE_ERROR */
|
||||||
|
postponed_error = std::current_exception();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t
|
||||||
|
CurlResponseHandlerAdapter::WriteFunction(char *ptr, std::size_t size,
|
||||||
|
std::size_t nmemb,
|
||||||
|
void *stream) noexcept
|
||||||
|
{
|
||||||
|
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
|
||||||
|
|
||||||
|
size *= nmemb;
|
||||||
|
if (size == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return c.DataReceived(ptr, size);
|
||||||
|
}
|
||||||
91
src/lib/curl/Adapter.hxx
Normal file
91
src/lib/curl/Adapter.hxx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008-2022 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 "Headers.hxx"
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
struct StringView;
|
||||||
|
class CurlEasy;
|
||||||
|
class CurlResponseHandler;
|
||||||
|
|
||||||
|
class CurlResponseHandlerAdapter {
|
||||||
|
CURL *curl;
|
||||||
|
|
||||||
|
CurlResponseHandler &handler;
|
||||||
|
|
||||||
|
Curl::Headers headers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception caught from within the WriteFunction() which
|
||||||
|
* will later be handled by Done().
|
||||||
|
*/
|
||||||
|
std::exception_ptr postponed_error;
|
||||||
|
|
||||||
|
/** error message provided by libcurl */
|
||||||
|
char error_buffer[CURL_ERROR_SIZE];
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
UNINITIALISED,
|
||||||
|
HEADERS,
|
||||||
|
BODY,
|
||||||
|
CLOSED,
|
||||||
|
} state = State::UNINITIALISED;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CurlResponseHandlerAdapter(CurlResponseHandler &_handler) noexcept
|
||||||
|
:handler(_handler) {}
|
||||||
|
|
||||||
|
void Install(CurlEasy &easy);
|
||||||
|
|
||||||
|
void Done(CURLcode result) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void FinishHeaders();
|
||||||
|
void FinishBody();
|
||||||
|
|
||||||
|
void HeaderFunction(StringView s) noexcept;
|
||||||
|
|
||||||
|
/** called by curl when a new header is available */
|
||||||
|
static std::size_t _HeaderFunction(char *ptr,
|
||||||
|
std::size_t size, std::size_t nmemb,
|
||||||
|
void *stream) noexcept;
|
||||||
|
|
||||||
|
std::size_t DataReceived(const void *ptr, std::size_t size) noexcept;
|
||||||
|
|
||||||
|
/** called by curl when new data is available */
|
||||||
|
static std::size_t WriteFunction(char *ptr,
|
||||||
|
std::size_t size, std::size_t nmemb,
|
||||||
|
void *stream) noexcept;
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -34,8 +34,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
void
|
void
|
||||||
DelegateCurlResponseHandler::OnHeaders(unsigned status,
|
DelegateCurlResponseHandler::OnHeaders(unsigned status, Curl::Headers &&headers)
|
||||||
std::multimap<std::string, std::string> &&headers)
|
|
||||||
{
|
{
|
||||||
parser = MakeParser(status, std::move(headers));
|
parser = MakeParser(status, std::move(headers));
|
||||||
assert(parser);
|
assert(parser);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2008-2022 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -27,8 +27,7 @@
|
|||||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef CURL_DELEGATE_HXX
|
#pragma once
|
||||||
#define CURL_DELEGATE_HXX
|
|
||||||
|
|
||||||
#include "Handler.hxx"
|
#include "Handler.hxx"
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ protected:
|
|||||||
* CurlResponseParser::OnError()).
|
* CurlResponseParser::OnError()).
|
||||||
*/
|
*/
|
||||||
virtual std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
virtual std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||||
std::multimap<std::string, std::string> &&headers) = 0;
|
Curl::Headers &&headers) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parser has finished parsing the response body. This
|
* The parser has finished parsing the response body. This
|
||||||
@@ -64,10 +63,7 @@ protected:
|
|||||||
virtual void FinishParser(std::unique_ptr<CurlResponseParser> p) = 0;
|
virtual void FinishParser(std::unique_ptr<CurlResponseParser> p) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void OnHeaders(unsigned status,
|
void OnHeaders(unsigned status, Curl::Headers &&headers) final;
|
||||||
std::multimap<std::string, std::string> &&headers) final;
|
|
||||||
void OnData(ConstBuffer<void> data) final;
|
void OnData(ConstBuffer<void> data) final;
|
||||||
void OnEnd() final;
|
void OnEnd() final;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -31,8 +31,7 @@
|
|||||||
#include "String.hxx"
|
#include "String.hxx"
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
EncodeForm(CURL *curl,
|
EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept
|
||||||
const std::multimap<std::string, std::string> &fields) noexcept
|
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,17 @@
|
|||||||
#ifndef CURL_FORM_HXX
|
#ifndef CURL_FORM_HXX
|
||||||
#define CURL_FORM_HXX
|
#define CURL_FORM_HXX
|
||||||
|
|
||||||
|
#include "Headers.hxx"
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode the given map of form fields to a
|
* Encode the given map of form fields to a
|
||||||
* "application/x-www-form-urlencoded" string.
|
* "application/x-www-form-urlencoded" string.
|
||||||
*/
|
*/
|
||||||
std::string
|
std::string
|
||||||
EncodeForm(CURL *curl,
|
EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept;
|
||||||
const std::multimap<std::string, std::string> &fields) noexcept;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -27,14 +27,12 @@
|
|||||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef CURL_HANDLER_HXX
|
#pragma once
|
||||||
#define CURL_HANDLER_HXX
|
|
||||||
|
|
||||||
|
#include "Headers.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <string>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronous response handler for a #CurlRequest.
|
* Asynchronous response handler for a #CurlRequest.
|
||||||
@@ -52,28 +50,31 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Status line and headers have been received.
|
* Status line and headers have been received.
|
||||||
|
*
|
||||||
|
* Exceptions thrown by this method will be passed to
|
||||||
|
* OnError(), aborting the request.
|
||||||
*/
|
*/
|
||||||
virtual void OnHeaders(unsigned status,
|
virtual void OnHeaders(unsigned status, Curl::Headers &&headers) = 0;
|
||||||
std::multimap<std::string, std::string> &&headers) = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response body data has been received.
|
* Response body data has been received.
|
||||||
*
|
*
|
||||||
* May throw #Pause (but nothing else).
|
* May throw #Pause.
|
||||||
|
*
|
||||||
|
* Other exceptions thrown by this method will be passed to
|
||||||
|
* OnError(), aborting the request.
|
||||||
*/
|
*/
|
||||||
virtual void OnData(ConstBuffer<void> data) = 0;
|
virtual void OnData(ConstBuffer<void> data) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The response has ended. The method is allowed delete the
|
* The response has ended. The method is allowed to delete the
|
||||||
* #CurlRequest here.
|
* #CurlRequest.
|
||||||
*/
|
*/
|
||||||
virtual void OnEnd() = 0;
|
virtual void OnEnd() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error has occurred. The method is allowed delete the
|
* An error has occurred. The method is allowed to delete the
|
||||||
* #CurlRequest here.
|
* #CurlRequest.
|
||||||
*/
|
*/
|
||||||
virtual void OnError(std::exception_ptr e) noexcept = 0;
|
virtual void OnError(std::exception_ptr e) noexcept = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
42
src/lib/curl/Headers.hxx
Normal file
42
src/lib/curl/Headers.hxx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020-2021 CM4all GmbH
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* author: Max Kellermann <mk@cm4all.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 <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Curl {
|
||||||
|
|
||||||
|
using Headers = std::multimap<std::string, std::string, std::less<>>;
|
||||||
|
|
||||||
|
} // namespace Curl
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -27,42 +27,27 @@
|
|||||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "Request.hxx"
|
#include "Request.hxx"
|
||||||
|
#include "Setup.hxx"
|
||||||
#include "Global.hxx"
|
#include "Global.hxx"
|
||||||
#include "Handler.hxx"
|
|
||||||
#include "event/Call.hxx"
|
#include "event/Call.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
|
||||||
#include "util/StringStrip.hxx"
|
|
||||||
#include "util/StringView.hxx"
|
|
||||||
#include "util/CharUtil.hxx"
|
|
||||||
#include "Version.h"
|
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include <string.h>
|
CurlRequest::CurlRequest(CurlGlobal &_global, CurlEasy _easy,
|
||||||
|
CurlResponseHandler &_handler)
|
||||||
|
:global(_global), handler(_handler), easy(std::move(_easy))
|
||||||
|
{
|
||||||
|
SetupEasy();
|
||||||
|
}
|
||||||
|
|
||||||
CurlRequest::CurlRequest(CurlGlobal &_global,
|
CurlRequest::CurlRequest(CurlGlobal &_global,
|
||||||
CurlResponseHandler &_handler)
|
CurlResponseHandler &_handler)
|
||||||
:global(_global), handler(_handler)
|
:global(_global), handler(_handler)
|
||||||
{
|
{
|
||||||
error_buffer[0] = 0;
|
SetupEasy();
|
||||||
|
|
||||||
easy.SetPrivate((void *)this);
|
|
||||||
easy.SetUserAgent("Music Player Daemon " VERSION);
|
|
||||||
easy.SetHeaderFunction(_HeaderFunction, this);
|
|
||||||
easy.SetWriteFunction(WriteFunction, this);
|
|
||||||
#if !defined(ANDROID) && !defined(_WIN32)
|
|
||||||
easy.SetOption(CURLOPT_NETRC, 1L);
|
|
||||||
#endif
|
|
||||||
easy.SetErrorBuffer(error_buffer);
|
|
||||||
easy.SetNoProgress();
|
|
||||||
easy.SetNoSignal();
|
|
||||||
easy.SetConnectTimeout(10);
|
|
||||||
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CurlRequest::~CurlRequest() noexcept
|
CurlRequest::~CurlRequest() noexcept
|
||||||
@@ -70,6 +55,16 @@ CurlRequest::~CurlRequest() noexcept
|
|||||||
FreeEasy();
|
FreeEasy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CurlRequest::SetupEasy()
|
||||||
|
{
|
||||||
|
easy.SetPrivate((void *)this);
|
||||||
|
|
||||||
|
handler.Install(easy);
|
||||||
|
|
||||||
|
Curl::Setup(easy);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CurlRequest::Start()
|
CurlRequest::Start()
|
||||||
{
|
{
|
||||||
@@ -125,135 +120,10 @@ CurlRequest::Resume() noexcept
|
|||||||
global.InvalidateSockets();
|
global.InvalidateSockets();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
CurlRequest::FinishHeaders()
|
|
||||||
{
|
|
||||||
if (state != State::HEADERS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
state = State::BODY;
|
|
||||||
|
|
||||||
long status = 0;
|
|
||||||
easy.GetInfo(CURLINFO_RESPONSE_CODE, &status);
|
|
||||||
|
|
||||||
handler.OnHeaders(status, std::move(headers));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
CurlRequest::FinishBody()
|
|
||||||
{
|
|
||||||
FinishHeaders();
|
|
||||||
|
|
||||||
if (state != State::BODY)
|
|
||||||
return;
|
|
||||||
|
|
||||||
state = State::CLOSED;
|
|
||||||
handler.OnEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
CurlRequest::Done(CURLcode result) noexcept
|
CurlRequest::Done(CURLcode result) noexcept
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
try {
|
handler.Done(result);
|
||||||
if (result != CURLE_OK) {
|
|
||||||
StripRight(error_buffer);
|
|
||||||
const char *msg = error_buffer;
|
|
||||||
if (*msg == 0)
|
|
||||||
msg = curl_easy_strerror(result);
|
|
||||||
throw FormatRuntimeError("CURL failed: %s", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
FinishBody();
|
|
||||||
} catch (...) {
|
|
||||||
state = State::CLOSED;
|
|
||||||
handler.OnError(std::current_exception());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::pure]]
|
|
||||||
static bool
|
|
||||||
IsResponseBoundaryHeader(StringView s) noexcept
|
|
||||||
{
|
|
||||||
return s.size > 5 && (s.StartsWith("HTTP/") ||
|
|
||||||
/* the proprietary "ICY 200 OK" is
|
|
||||||
emitted by Shoutcast */
|
|
||||||
s.StartsWith("ICY 2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void
|
|
||||||
CurlRequest::HeaderFunction(StringView s) noexcept
|
|
||||||
{
|
|
||||||
if (state > State::HEADERS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (IsResponseBoundaryHeader(s)) {
|
|
||||||
/* this is the boundary to a new response, for example
|
|
||||||
after a redirect */
|
|
||||||
headers.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *header = s.data;
|
|
||||||
const char *end = StripRight(header, header + s.size);
|
|
||||||
|
|
||||||
const char *value = s.Find(':');
|
|
||||||
if (value == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string name(header, value);
|
|
||||||
std::transform(name.begin(), name.end(), name.begin(),
|
|
||||||
static_cast<char(*)(char)>(ToLowerASCII));
|
|
||||||
|
|
||||||
/* skip the colon */
|
|
||||||
|
|
||||||
++value;
|
|
||||||
|
|
||||||
/* strip the value */
|
|
||||||
|
|
||||||
value = StripLeft(value, end);
|
|
||||||
end = StripRight(value, end);
|
|
||||||
|
|
||||||
headers.emplace(std::move(name), std::string(value, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
CurlRequest::_HeaderFunction(char *ptr, size_t size, size_t nmemb,
|
|
||||||
void *stream) noexcept
|
|
||||||
{
|
|
||||||
CurlRequest &c = *(CurlRequest *)stream;
|
|
||||||
|
|
||||||
size *= nmemb;
|
|
||||||
|
|
||||||
c.HeaderFunction({ptr, size});
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline size_t
|
|
||||||
CurlRequest::DataReceived(const void *ptr, size_t received_size) noexcept
|
|
||||||
{
|
|
||||||
assert(received_size > 0);
|
|
||||||
|
|
||||||
try {
|
|
||||||
FinishHeaders();
|
|
||||||
handler.OnData({ptr, received_size});
|
|
||||||
return received_size;
|
|
||||||
} catch (CurlResponseHandler::Pause) {
|
|
||||||
return CURL_WRITEFUNC_PAUSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
CurlRequest::WriteFunction(char *ptr, size_t size, size_t nmemb,
|
|
||||||
void *stream) noexcept
|
|
||||||
{
|
|
||||||
CurlRequest &c = *(CurlRequest *)stream;
|
|
||||||
|
|
||||||
size *= nmemb;
|
|
||||||
if (size == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return c.DataReceived(ptr, size);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2008-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -31,39 +31,34 @@
|
|||||||
#define CURL_REQUEST_HXX
|
#define CURL_REQUEST_HXX
|
||||||
|
|
||||||
#include "Easy.hxx"
|
#include "Easy.hxx"
|
||||||
|
#include "Adapter.hxx"
|
||||||
|
|
||||||
#include <map>
|
#include <cstddef>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
struct StringView;
|
struct StringView;
|
||||||
class CurlGlobal;
|
class CurlGlobal;
|
||||||
class CurlResponseHandler;
|
class CurlResponseHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A non-blocking HTTP request integrated via #CurlGlobal into the
|
||||||
|
* #EventLoop.
|
||||||
|
*
|
||||||
|
* To start sending the request, call Start().
|
||||||
|
*/
|
||||||
class CurlRequest final {
|
class CurlRequest final {
|
||||||
CurlGlobal &global;
|
CurlGlobal &global;
|
||||||
|
|
||||||
CurlResponseHandler &handler;
|
CurlResponseHandlerAdapter handler;
|
||||||
|
|
||||||
/** the curl handle */
|
/** the curl handle */
|
||||||
CurlEasy easy;
|
CurlEasy easy;
|
||||||
|
|
||||||
enum class State {
|
|
||||||
HEADERS,
|
|
||||||
BODY,
|
|
||||||
CLOSED,
|
|
||||||
} state = State::HEADERS;
|
|
||||||
|
|
||||||
std::multimap<std::string, std::string> headers;
|
|
||||||
|
|
||||||
/** error message provided by libcurl */
|
|
||||||
char error_buffer[CURL_ERROR_SIZE];
|
|
||||||
|
|
||||||
bool registered = false;
|
bool registered = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
CurlRequest(CurlGlobal &_global, CurlEasy easy,
|
||||||
* To start sending the request, call Start().
|
CurlResponseHandler &_handler);
|
||||||
*/
|
|
||||||
CurlRequest(CurlGlobal &_global,
|
CurlRequest(CurlGlobal &_global,
|
||||||
CurlResponseHandler &_handler);
|
CurlResponseHandler &_handler);
|
||||||
|
|
||||||
@@ -136,7 +131,7 @@ public:
|
|||||||
easy.SetPost(value);
|
easy.SetPost(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetRequestBody(const void *data, size_t size) {
|
void SetRequestBody(const void *data, std::size_t size) {
|
||||||
easy.SetRequestBody(data, size);
|
easy.SetRequestBody(data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +143,8 @@ public:
|
|||||||
void Done(CURLcode result) noexcept;
|
void Done(CURLcode result) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void SetupEasy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees the current "libcurl easy" handle, and everything
|
* Frees the current "libcurl easy" handle, and everything
|
||||||
* associated with it.
|
* associated with it.
|
||||||
@@ -156,18 +153,6 @@ private:
|
|||||||
|
|
||||||
void FinishHeaders();
|
void FinishHeaders();
|
||||||
void FinishBody();
|
void FinishBody();
|
||||||
|
|
||||||
size_t DataReceived(const void *ptr, size_t size) noexcept;
|
|
||||||
|
|
||||||
void HeaderFunction(StringView s) noexcept;
|
|
||||||
|
|
||||||
/** called by curl when new data is available */
|
|
||||||
static size_t _HeaderFunction(char *ptr, size_t size, size_t nmemb,
|
|
||||||
void *stream) noexcept;
|
|
||||||
|
|
||||||
/** called by curl when new data is available */
|
|
||||||
static size_t WriteFunction(char *ptr, size_t size, size_t nmemb,
|
|
||||||
void *stream) noexcept;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
51
src/lib/curl/Setup.cxx
Normal file
51
src/lib/curl/Setup.cxx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008-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 "Setup.hxx"
|
||||||
|
#include "Easy.hxx"
|
||||||
|
#include "Version.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
namespace Curl {
|
||||||
|
|
||||||
|
void
|
||||||
|
Setup(CurlEasy &easy)
|
||||||
|
{
|
||||||
|
easy.SetUserAgent("Music Player Daemon " VERSION);
|
||||||
|
#if !defined(ANDROID) && !defined(_WIN32)
|
||||||
|
easy.SetOption(CURLOPT_NETRC, 1L);
|
||||||
|
#endif
|
||||||
|
easy.SetNoProgress();
|
||||||
|
easy.SetNoSignal();
|
||||||
|
easy.SetConnectTimeout(10);
|
||||||
|
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Curl
|
||||||
39
src/lib/curl/Setup.hxx
Normal file
39
src/lib/curl/Setup.hxx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008-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
|
||||||
|
|
||||||
|
class CurlEasy;
|
||||||
|
|
||||||
|
namespace Curl {
|
||||||
|
|
||||||
|
void
|
||||||
|
Setup(CurlEasy &easy);
|
||||||
|
|
||||||
|
} // namespace Curl
|
||||||
@@ -18,6 +18,8 @@ curl = static_library(
|
|||||||
'Init.cxx',
|
'Init.cxx',
|
||||||
'Global.cxx',
|
'Global.cxx',
|
||||||
'Request.cxx',
|
'Request.cxx',
|
||||||
|
'Setup.cxx',
|
||||||
|
'Adapter.cxx',
|
||||||
'Escape.cxx',
|
'Escape.cxx',
|
||||||
'Form.cxx',
|
'Form.cxx',
|
||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
--- curl-7.75.0.orig/CMakeLists.txt 2021-02-02 09:26:24.000000000 +0100
|
Index: curl-7.84.0/CMakeLists.txt
|
||||||
+++ curl-7.75.0/CMakeLists.txt 2021-03-25 20:17:25.445684029 +0100
|
===================================================================
|
||||||
@@ -1453,7 +1453,7 @@
|
--- curl-7.84.0.orig/CMakeLists.txt
|
||||||
|
+++ curl-7.84.0/CMakeLists.txt
|
||||||
|
@@ -1536,7 +1536,7 @@ set(includedir "\${prefix}/
|
||||||
set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
|
set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
|
||||||
set(LIBCURL_LIBS "")
|
set(LIBCURL_LIBS "")
|
||||||
set(libdir "${CMAKE_INSTALL_PREFIX}/lib")
|
set(libdir "${CMAKE_INSTALL_PREFIX}/lib")
|
||||||
@@ -8,4 +10,4 @@
|
|||||||
+foreach(_lib ${CURL_LIBS})
|
+foreach(_lib ${CURL_LIBS})
|
||||||
if(TARGET "${_lib}")
|
if(TARGET "${_lib}")
|
||||||
set(_libname "${_lib}")
|
set(_libname "${_lib}")
|
||||||
get_target_property(_libtype "${_libname}" TYPE)
|
get_target_property(_imported "${_libname}" IMPORTED)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
Index: curl-7.71.1/lib/url.c
|
Index: curl-7.84.0/lib/url.c
|
||||||
===================================================================
|
===================================================================
|
||||||
--- curl-7.71.1.orig/lib/url.c
|
--- curl-7.84.0.orig/lib/url.c
|
||||||
+++ curl-7.71.1/lib/url.c
|
+++ curl-7.84.0/lib/url.c
|
||||||
@@ -2871,6 +2871,7 @@
|
@@ -3003,6 +3003,7 @@ static CURLcode override_login(struct Cu
|
||||||
}
|
|
||||||
|
|
||||||
|
#ifndef CURL_DISABLE_NETRC
|
||||||
conn->bits.netrc = FALSE;
|
conn->bits.netrc = FALSE;
|
||||||
+#ifndef __BIONIC__
|
+#ifndef __BIONIC__
|
||||||
if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) {
|
if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) {
|
||||||
bool netrc_user_changed = FALSE;
|
bool netrc_user_changed = FALSE;
|
||||||
bool netrc_passwd_changed = FALSE;
|
bool netrc_passwd_changed = FALSE;
|
||||||
@@ -2895,6 +2896,7 @@
|
@@ -3079,6 +3080,7 @@ static CURLcode override_login(struct Cu
|
||||||
conn->bits.user_passwd = TRUE; /* enable user+password */
|
return CURLE_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+#endif
|
+#endif
|
||||||
|
|
||||||
/* for updated strings, we update them in the URL */
|
return CURLE_OK;
|
||||||
if(*userp) {
|
}
|
||||||
|
|||||||
@@ -48,7 +48,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
|
|||||||
Frame frame;
|
Frame frame;
|
||||||
frame->format = ToFfmpegSampleFormat(in_audio_format.format);
|
frame->format = ToFfmpegSampleFormat(in_audio_format.format);
|
||||||
frame->sample_rate = in_audio_format.sample_rate;
|
frame->sample_rate = in_audio_format.sample_rate;
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
av_channel_layout_default(&frame->ch_layout, in_audio_format.channels);
|
||||||
|
#else
|
||||||
frame->channels = in_audio_format.channels;
|
frame->channels = in_audio_format.channels;
|
||||||
|
#endif
|
||||||
frame->nb_samples = 1;
|
frame->nb_samples = 1;
|
||||||
|
|
||||||
frame.GetBuffer();
|
frame.GetBuffer();
|
||||||
@@ -75,8 +79,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
|
|||||||
if (sample_format == SampleFormat::UNDEFINED)
|
if (sample_format == SampleFormat::UNDEFINED)
|
||||||
throw std::runtime_error("Unsupported FFmpeg sample format");
|
throw std::runtime_error("Unsupported FFmpeg sample format");
|
||||||
|
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
const unsigned out_channels = frame->ch_layout.nb_channels;
|
||||||
|
#else
|
||||||
|
const unsigned out_channels = frame->channels;
|
||||||
|
#endif
|
||||||
|
|
||||||
return CheckAudioFormat(frame->sample_rate, sample_format,
|
return CheckAudioFormat(frame->sample_rate, sample_format,
|
||||||
frame->channels);
|
out_channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ffmpeg
|
} // namespace Ffmpeg
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer)
|
|||||||
assert(frame.nb_samples > 0);
|
assert(frame.nb_samples > 0);
|
||||||
|
|
||||||
const auto format = AVSampleFormat(frame.format);
|
const auto format = AVSampleFormat(frame.format);
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||||
|
const unsigned channels = frame.ch_layout.nb_channels;
|
||||||
|
#else
|
||||||
const unsigned channels = frame.channels;
|
const unsigned channels = frame.channels;
|
||||||
|
#endif
|
||||||
const std::size_t n_frames = frame.nb_samples;
|
const std::size_t n_frames = frame.nb_samples;
|
||||||
|
|
||||||
int plane_size;
|
int plane_size;
|
||||||
|
|||||||
@@ -12,17 +12,31 @@ if is_windows
|
|||||||
icu_sources += 'Win32.cxx'
|
icu_sources += 'Win32.cxx'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
iconv_dep = []
|
||||||
if icu_dep.found()
|
if icu_dep.found()
|
||||||
icu_sources += [
|
icu_sources += [
|
||||||
'Util.cxx',
|
'Util.cxx',
|
||||||
'Init.cxx',
|
'Init.cxx',
|
||||||
]
|
]
|
||||||
|
else
|
||||||
|
if meson.version().version_compare('>= 0.60')
|
||||||
|
iconv_dep = dependency('iconv', required: get_option('iconv'))
|
||||||
|
conf.set('HAVE_ICONV', iconv_dep.found())
|
||||||
elif not get_option('iconv').disabled()
|
elif not get_option('iconv').disabled()
|
||||||
have_iconv = compiler.has_function('iconv', prefix : '#include <iconv.h>')
|
iconv_open_snippet = '''#include <iconv.h>
|
||||||
conf.set('HAVE_ICONV', have_iconv)
|
int main() {
|
||||||
|
iconv_open("","");
|
||||||
|
}'''
|
||||||
|
have_iconv = compiler.links(iconv_open_snippet, name: 'iconv_open')
|
||||||
|
if not have_iconv
|
||||||
|
iconv_dep = compiler.find_library('iconv', required: false)
|
||||||
|
have_iconv = compiler.links(iconv_open_snippet, dependencies: iconv_dep, name: 'iconv_open')
|
||||||
|
endif
|
||||||
if not have_iconv and get_option('iconv').enabled()
|
if not have_iconv and get_option('iconv').enabled()
|
||||||
error('iconv() not available')
|
error('iconv() not available')
|
||||||
endif
|
endif
|
||||||
|
conf.set('HAVE_ICONV', have_iconv)
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
icu = static_library(
|
icu = static_library(
|
||||||
@@ -31,6 +45,7 @@ icu = static_library(
|
|||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
icu_dep,
|
icu_dep,
|
||||||
|
iconv_dep,
|
||||||
fmt_dep,
|
fmt_dep,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
/* libupnp versions until 1.10.1 redefine "bool" and "true" */
|
/* libupnp versions until 1.10.1 redefine "bool" and "true" */
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wkeyword-macro"
|
#pragma GCC diagnostic ignored "-Wkeyword-macro"
|
||||||
|
|
||||||
|
/* libupnp 1.8.4 uses a flawed kludge to suppress this warning in
|
||||||
|
inline function __list_add_valid() */
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <upnp.h>
|
#include <upnp.h>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ UPnPDeviceDirectory::Downloader::Destroy() noexcept
|
|||||||
|
|
||||||
void
|
void
|
||||||
UPnPDeviceDirectory::Downloader::OnHeaders(unsigned status,
|
UPnPDeviceDirectory::Downloader::OnHeaders(unsigned status,
|
||||||
std::multimap<std::string, std::string> &&)
|
Curl::Headers &&)
|
||||||
{
|
{
|
||||||
if (status != 200) {
|
if (status != 200) {
|
||||||
Destroy();
|
Destroy();
|
||||||
|
|||||||
@@ -113,8 +113,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* virtual methods from CurlResponseHandler */
|
/* virtual methods from CurlResponseHandler */
|
||||||
void OnHeaders(unsigned status,
|
void OnHeaders(unsigned status, Curl::Headers &&headers) override;
|
||||||
std::multimap<std::string, std::string> &&headers) override;
|
|
||||||
void OnData(ConstBuffer<void> data) override;
|
void OnData(ConstBuffer<void> data) override;
|
||||||
void OnEnd() override;
|
void OnEnd() override;
|
||||||
void OnError(std::exception_ptr e) noexcept override;
|
void OnError(std::exception_ptr e) noexcept override;
|
||||||
|
|||||||
@@ -35,13 +35,7 @@ static unsigned upnp_ref;
|
|||||||
static void
|
static void
|
||||||
DoInit(const char* iface)
|
DoInit(const char* iface)
|
||||||
{
|
{
|
||||||
|
if (auto code = UpnpInit2(iface, 0); code != UPNP_E_SUCCESS)
|
||||||
#ifdef UPNP_ENABLE_IPV6
|
|
||||||
auto code = UpnpInit2(iface, 0);
|
|
||||||
#else
|
|
||||||
auto code = UpnpInit(iface, 0);
|
|
||||||
#endif
|
|
||||||
if (code != UPNP_E_SUCCESS)
|
|
||||||
throw FormatRuntimeError("UpnpInit() failed: %s",
|
throw FormatRuntimeError("UpnpInit() failed: %s",
|
||||||
UpnpGetErrorMessage(code));
|
UpnpGetErrorMessage(code));
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ OggSyncState::ExpectPageIn(ogg_stream_state &os)
|
|||||||
bool
|
bool
|
||||||
OggSyncState::ExpectPageSeek(ogg_page &page)
|
OggSyncState::ExpectPageSeek(ogg_page &page)
|
||||||
{
|
{
|
||||||
size_t remaining_skipped = 32768;
|
size_t remaining_skipped = 65536;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int r = ogg_sync_pageseek(&oy, &page);
|
int r = ogg_sync_pageseek(&oy, &page);
|
||||||
|
|||||||
@@ -17,14 +17,11 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Volume.hxx"
|
#include "Memento.hxx"
|
||||||
#include "output/MultipleOutputs.hxx"
|
#include "output/MultipleOutputs.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/Domain.hxx"
|
|
||||||
#include "system/PeriodClock.hxx"
|
|
||||||
#include "io/BufferedOutputStream.hxx"
|
#include "io/BufferedOutputStream.hxx"
|
||||||
#include "Log.hxx"
|
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
@@ -32,24 +29,8 @@
|
|||||||
|
|
||||||
#define SW_VOLUME_STATE "sw_volume: "
|
#define SW_VOLUME_STATE "sw_volume: "
|
||||||
|
|
||||||
static constexpr Domain volume_domain("volume");
|
|
||||||
|
|
||||||
static unsigned volume_software_set = 100;
|
|
||||||
|
|
||||||
/** the cached hardware mixer value; invalid if negative */
|
|
||||||
static int last_hardware_volume = -1;
|
|
||||||
/** the age of #last_hardware_volume */
|
|
||||||
static PeriodClock hardware_volume_clock;
|
|
||||||
|
|
||||||
void
|
|
||||||
InvalidateHardwareVolume() noexcept
|
|
||||||
{
|
|
||||||
/* flush the hardware volume cache */
|
|
||||||
last_hardware_volume = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
volume_level_get(const MultipleOutputs &outputs) noexcept
|
MixerMemento::GetVolume(const MultipleOutputs &outputs) noexcept
|
||||||
{
|
{
|
||||||
if (last_hardware_volume >= 0 &&
|
if (last_hardware_volume >= 0 &&
|
||||||
!hardware_volume_clock.CheckUpdate(std::chrono::seconds(1)))
|
!hardware_volume_clock.CheckUpdate(std::chrono::seconds(1)))
|
||||||
@@ -60,8 +41,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept
|
|||||||
return last_hardware_volume;
|
return last_hardware_volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
inline bool
|
||||||
software_volume_change(MultipleOutputs &outputs, unsigned volume)
|
MixerMemento::SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume)
|
||||||
{
|
{
|
||||||
assert(volume <= 100);
|
assert(volume <= 100);
|
||||||
|
|
||||||
@@ -71,29 +52,27 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
inline void
|
||||||
hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
|
MixerMemento::SetHardwareVolume(MultipleOutputs &outputs, unsigned volume)
|
||||||
{
|
{
|
||||||
/* reset the cache */
|
/* reset the cache */
|
||||||
last_hardware_volume = -1;
|
last_hardware_volume = -1;
|
||||||
|
|
||||||
return outputs.SetVolume(volume);
|
outputs.SetVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
void
|
||||||
volume_level_change(MultipleOutputs &outputs, unsigned volume)
|
MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume)
|
||||||
{
|
{
|
||||||
assert(volume <= 100);
|
assert(volume <= 100);
|
||||||
|
|
||||||
volume_software_set = volume;
|
volume_software_set = volume;
|
||||||
|
|
||||||
idle_add(IDLE_MIXER);
|
SetHardwareVolume(outputs, volume);
|
||||||
|
|
||||||
return hardware_volume_change(outputs, volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
read_sw_volume_state(const char *line, MultipleOutputs &outputs)
|
MixerMemento::LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs)
|
||||||
{
|
{
|
||||||
char *end = nullptr;
|
char *end = nullptr;
|
||||||
long int sv;
|
long int sv;
|
||||||
@@ -104,21 +83,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs)
|
|||||||
|
|
||||||
sv = strtol(line, &end, 10);
|
sv = strtol(line, &end, 10);
|
||||||
if (*end == 0 && sv >= 0 && sv <= 100)
|
if (*end == 0 && sv >= 0 && sv <= 100)
|
||||||
software_volume_change(outputs, sv);
|
SetSoftwareVolume(outputs, sv);
|
||||||
else
|
|
||||||
FmtWarning(volume_domain,
|
|
||||||
"Can't parse software volume: {}", line);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
save_sw_volume_state(BufferedOutputStream &os)
|
MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const
|
||||||
{
|
{
|
||||||
os.Format(SW_VOLUME_STATE "%u\n", volume_software_set);
|
os.Format(SW_VOLUME_STATE "%u\n", volume_software_set);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned
|
|
||||||
sw_volume_state_get_hash() noexcept
|
|
||||||
{
|
|
||||||
return volume_software_set;
|
|
||||||
}
|
|
||||||
75
src/mixer/Memento.hxx
Normal file
75
src/mixer/Memento.hxx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2021 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "system/PeriodClock.hxx"
|
||||||
|
|
||||||
|
class MultipleOutputs;
|
||||||
|
class BufferedOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for hardware/software volume levels.
|
||||||
|
*/
|
||||||
|
class MixerMemento {
|
||||||
|
unsigned volume_software_set = 100;
|
||||||
|
|
||||||
|
/** the cached hardware mixer value; invalid if negative */
|
||||||
|
int last_hardware_volume = -1;
|
||||||
|
|
||||||
|
/** the age of #last_hardware_volume */
|
||||||
|
PeriodClock hardware_volume_clock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Flush the hardware volume cache.
|
||||||
|
*/
|
||||||
|
void InvalidateHardwareVolume() noexcept {
|
||||||
|
last_hardware_volume = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[gnu::pure]]
|
||||||
|
int GetVolume(const MultipleOutputs &outputs) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws on error.
|
||||||
|
*
|
||||||
|
* Note: the caller is responsible for emitting #IDLE_MIXER.
|
||||||
|
*/
|
||||||
|
void SetVolume(MultipleOutputs &outputs, unsigned volume);
|
||||||
|
|
||||||
|
bool LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs);
|
||||||
|
|
||||||
|
void SaveSoftwareVolumeState(BufferedOutputStream &os) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a hash number for the current state of the software
|
||||||
|
* volume control. This is used by timer_save_state_file() to
|
||||||
|
* determine whether the state has changed and the state file should
|
||||||
|
* be saved.
|
||||||
|
*/
|
||||||
|
[[gnu::pure]]
|
||||||
|
unsigned GetSoftwareVolumeStateHash() const noexcept {
|
||||||
|
return volume_software_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume);
|
||||||
|
void SetHardwareVolume(MultipleOutputs &outputs, unsigned volume);
|
||||||
|
};
|
||||||
@@ -34,13 +34,15 @@ gcc_pure
|
|||||||
static int
|
static int
|
||||||
output_mixer_get_volume(const AudioOutputControl &ao) noexcept
|
output_mixer_get_volume(const AudioOutputControl &ao) noexcept
|
||||||
{
|
{
|
||||||
if (!ao.IsEnabled())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
auto *mixer = ao.GetMixer();
|
auto *mixer = ao.GetMixer();
|
||||||
if (mixer == nullptr)
|
if (mixer == nullptr)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
/* software mixers are always considered, even if they are
|
||||||
|
disabled */
|
||||||
|
if (!ao.IsEnabled() && !mixer->IsPlugin(software_mixer_plugin))
|
||||||
|
return -1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return mixer_get_volume(mixer);
|
return mixer_get_volume(mixer);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
@@ -71,40 +73,77 @@ MultipleOutputs::GetVolume() const noexcept
|
|||||||
return total / ok;
|
return total / ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
enum class SetVolumeResult {
|
||||||
output_mixer_set_volume(AudioOutputControl &ao, unsigned volume) noexcept
|
NO_MIXER,
|
||||||
|
DISABLED,
|
||||||
|
ERROR,
|
||||||
|
OK,
|
||||||
|
};
|
||||||
|
|
||||||
|
static SetVolumeResult
|
||||||
|
output_mixer_set_volume(AudioOutputControl &ao, unsigned volume)
|
||||||
{
|
{
|
||||||
assert(volume <= 100);
|
assert(volume <= 100);
|
||||||
|
|
||||||
if (!ao.IsEnabled())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto *mixer = ao.GetMixer();
|
auto *mixer = ao.GetMixer();
|
||||||
if (mixer == nullptr)
|
if (mixer == nullptr)
|
||||||
return false;
|
return SetVolumeResult::NO_MIXER;
|
||||||
|
|
||||||
|
/* software mixers are always updated, even if they are
|
||||||
|
disabled */
|
||||||
|
if (!mixer->IsPlugin(software_mixer_plugin) &&
|
||||||
|
/* "global" mixers can be used even if the output hasn't
|
||||||
|
been used yet */
|
||||||
|
!(mixer->IsGlobal() ? ao.IsEnabled() : ao.IsReallyEnabled()))
|
||||||
|
return SetVolumeResult::DISABLED;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mixer_set_volume(mixer, volume);
|
mixer_set_volume(mixer, volume);
|
||||||
return true;
|
return SetVolumeResult::OK;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
FmtError(mixer_domain,
|
FmtError(mixer_domain,
|
||||||
"Failed to set mixer for '{}': {}",
|
"Failed to set mixer for '{}': {}",
|
||||||
ao.GetName(), std::current_exception());
|
ao.GetName(), std::current_exception());
|
||||||
return false;
|
std::throw_with_nested(std::runtime_error(fmt::format("Failed to set mixer for '{}'",
|
||||||
|
ao.GetName())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
void
|
||||||
MultipleOutputs::SetVolume(unsigned volume) noexcept
|
MultipleOutputs::SetVolume(unsigned volume)
|
||||||
{
|
{
|
||||||
assert(volume <= 100);
|
assert(volume <= 100);
|
||||||
|
|
||||||
bool success = false;
|
SetVolumeResult result = SetVolumeResult::NO_MIXER;
|
||||||
for (const auto &ao : outputs)
|
std::exception_ptr error;
|
||||||
success = output_mixer_set_volume(*ao, volume)
|
|
||||||
|| success;
|
|
||||||
|
|
||||||
return success;
|
for (const auto &ao : outputs) {
|
||||||
|
try {
|
||||||
|
auto r = output_mixer_set_volume(*ao, volume);
|
||||||
|
if (r > result)
|
||||||
|
result = r;
|
||||||
|
} catch (...) {
|
||||||
|
/* remember the first error */
|
||||||
|
if (!error) {
|
||||||
|
error = std::current_exception();
|
||||||
|
result = SetVolumeResult::ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case SetVolumeResult::NO_MIXER:
|
||||||
|
throw std::runtime_error{"No mixer"};
|
||||||
|
|
||||||
|
case SetVolumeResult::DISABLED:
|
||||||
|
throw std::runtime_error{"All outputs are disabled"};
|
||||||
|
|
||||||
|
case SetVolumeResult::ERROR:
|
||||||
|
std::rethrow_exception(error);
|
||||||
|
|
||||||
|
case SetVolumeResult::OK:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -149,8 +188,8 @@ MultipleOutputs::SetSoftwareVolume(unsigned volume) noexcept
|
|||||||
auto *mixer = ao->GetMixer();
|
auto *mixer = ao->GetMixer();
|
||||||
|
|
||||||
if (mixer != nullptr &&
|
if (mixer != nullptr &&
|
||||||
(&mixer->plugin == &software_mixer_plugin ||
|
(mixer->IsPlugin(software_mixer_plugin) ||
|
||||||
&mixer->plugin == &null_mixer_plugin))
|
mixer->IsPlugin(null_mixer_plugin)))
|
||||||
mixer_set_volume(mixer, volume);
|
mixer_set_volume(mixer, volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ mixer_open(Mixer *mixer)
|
|||||||
try {
|
try {
|
||||||
mixer->Open();
|
mixer->Open();
|
||||||
mixer->open = true;
|
mixer->open = true;
|
||||||
mixer->failed = false;
|
mixer->failure = {};
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
mixer->failed = true;
|
mixer->failure = std::current_exception();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,7 @@ mixer_close_internal(Mixer *mixer)
|
|||||||
|
|
||||||
mixer->Close();
|
mixer->Close();
|
||||||
mixer->open = false;
|
mixer->open = false;
|
||||||
|
mixer->failure = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -91,24 +92,10 @@ mixer_close(Mixer *mixer)
|
|||||||
void
|
void
|
||||||
mixer_auto_close(Mixer *mixer)
|
mixer_auto_close(Mixer *mixer)
|
||||||
{
|
{
|
||||||
if (!mixer->plugin.global)
|
if (!mixer->IsGlobal())
|
||||||
mixer_close(mixer);
|
mixer_close(mixer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Close the mixer due to failure. The mutex must be locked before
|
|
||||||
* calling this function.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
mixer_failed(Mixer *mixer)
|
|
||||||
{
|
|
||||||
assert(mixer->open);
|
|
||||||
|
|
||||||
mixer_close_internal(mixer);
|
|
||||||
|
|
||||||
mixer->failed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
mixer_get_volume(Mixer *mixer)
|
mixer_get_volume(Mixer *mixer)
|
||||||
{
|
{
|
||||||
@@ -116,7 +103,7 @@ mixer_get_volume(Mixer *mixer)
|
|||||||
|
|
||||||
assert(mixer != nullptr);
|
assert(mixer != nullptr);
|
||||||
|
|
||||||
if (mixer->plugin.global && !mixer->failed)
|
if (mixer->IsGlobal() && !mixer->failure)
|
||||||
mixer_open(mixer);
|
mixer_open(mixer);
|
||||||
|
|
||||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||||
@@ -125,7 +112,8 @@ mixer_get_volume(Mixer *mixer)
|
|||||||
try {
|
try {
|
||||||
volume = mixer->GetVolume();
|
volume = mixer->GetVolume();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
mixer_failed(mixer);
|
mixer_close_internal(mixer);
|
||||||
|
mixer->failure = std::current_exception();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@@ -140,11 +128,13 @@ mixer_set_volume(Mixer *mixer, unsigned volume)
|
|||||||
assert(mixer != nullptr);
|
assert(mixer != nullptr);
|
||||||
assert(volume <= 100);
|
assert(volume <= 100);
|
||||||
|
|
||||||
if (mixer->plugin.global && !mixer->failed)
|
if (mixer->IsGlobal() && !mixer->failure)
|
||||||
mixer_open(mixer);
|
mixer_open(mixer);
|
||||||
|
|
||||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||||
|
|
||||||
if (mixer->open)
|
if (mixer->open)
|
||||||
mixer->SetVolume(volume);
|
mixer->SetVolume(volume);
|
||||||
|
else if (mixer->failure)
|
||||||
|
std::rethrow_exception(mixer->failure);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
class MixerListener;
|
class MixerListener;
|
||||||
|
|
||||||
class Mixer {
|
class Mixer {
|
||||||
@@ -39,17 +41,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains error details if this mixer has failed. If set,
|
||||||
|
* it should not be reopened automatically.
|
||||||
|
*/
|
||||||
|
std::exception_ptr failure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the mixer device currently open?
|
* Is the mixer device currently open?
|
||||||
*/
|
*/
|
||||||
bool open = false;
|
bool open = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Has this mixer failed, and should not be reopened
|
|
||||||
* automatically?
|
|
||||||
*/
|
|
||||||
bool failed = false;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Mixer(const MixerPlugin &_plugin,
|
explicit Mixer(const MixerPlugin &_plugin,
|
||||||
MixerListener &_listener) noexcept
|
MixerListener &_listener) noexcept
|
||||||
@@ -63,6 +65,10 @@ public:
|
|||||||
return &plugin == &other;
|
return &plugin == &other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsGlobal() const noexcept {
|
||||||
|
return plugin.global;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open mixer device
|
* Open mixer device
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2021 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MPD_VOLUME_HXX
|
|
||||||
#define MPD_VOLUME_HXX
|
|
||||||
|
|
||||||
class MultipleOutputs;
|
|
||||||
class BufferedOutputStream;
|
|
||||||
|
|
||||||
void
|
|
||||||
InvalidateHardwareVolume() noexcept;
|
|
||||||
|
|
||||||
[[gnu::pure]]
|
|
||||||
int
|
|
||||||
volume_level_get(const MultipleOutputs &outputs) noexcept;
|
|
||||||
|
|
||||||
bool
|
|
||||||
volume_level_change(MultipleOutputs &outputs, unsigned volume);
|
|
||||||
|
|
||||||
bool
|
|
||||||
read_sw_volume_state(const char *line, MultipleOutputs &outputs);
|
|
||||||
|
|
||||||
void
|
|
||||||
save_sw_volume_state(BufferedOutputStream &os);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a hash number for the current state of the software
|
|
||||||
* volume control. This is used by timer_save_state_file() to
|
|
||||||
* determine whether the state has changed and the state file should
|
|
||||||
* be saved.
|
|
||||||
*/
|
|
||||||
[[gnu::pure]]
|
|
||||||
unsigned
|
|
||||||
sw_volume_state_get_hash() noexcept;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -288,6 +288,13 @@ public:
|
|||||||
return !output;
|
return !output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caller must lock the mutex.
|
||||||
|
*/
|
||||||
|
bool IsReallyEnabled() const noexcept {
|
||||||
|
return really_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caller must lock the mutex.
|
* Caller must lock the mutex.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -141,10 +141,11 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Sets the volume on all available mixers.
|
* Sets the volume on all available mixers.
|
||||||
*
|
*
|
||||||
|
* Throws on error.
|
||||||
|
*
|
||||||
* @param volume the volume (range 0..100)
|
* @param volume the volume (range 0..100)
|
||||||
* @return true on success, false on failure
|
|
||||||
*/
|
*/
|
||||||
bool SetVolume(unsigned volume) noexcept;
|
void SetVolume(unsigned volume);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to GetVolume(), but gets the volume only for
|
* Similar to GetVolume(), but gets the volume only for
|
||||||
|
|||||||
@@ -28,13 +28,15 @@
|
|||||||
#include "MultipleOutputs.hxx"
|
#include "MultipleOutputs.hxx"
|
||||||
#include "Client.hxx"
|
#include "Client.hxx"
|
||||||
#include "mixer/MixerControl.hxx"
|
#include "mixer/MixerControl.hxx"
|
||||||
#include "mixer/Volume.hxx"
|
#include "mixer/Memento.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
|
|
||||||
extern unsigned audio_output_state_version;
|
extern unsigned audio_output_state_version;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
audio_output_enable_index(MultipleOutputs &outputs,
|
||||||
|
MixerMemento &mixer_memento,
|
||||||
|
unsigned idx)
|
||||||
{
|
{
|
||||||
if (idx >= outputs.Size())
|
if (idx >= outputs.Size())
|
||||||
return false;
|
return false;
|
||||||
@@ -46,7 +48,7 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
|||||||
idle_add(IDLE_OUTPUT);
|
idle_add(IDLE_OUTPUT);
|
||||||
|
|
||||||
if (ao.GetMixer() != nullptr) {
|
if (ao.GetMixer() != nullptr) {
|
||||||
InvalidateHardwareVolume();
|
mixer_memento.InvalidateHardwareVolume();
|
||||||
idle_add(IDLE_MIXER);
|
idle_add(IDLE_MIXER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +60,9 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
audio_output_disable_index(MultipleOutputs &outputs,
|
||||||
|
MixerMemento &mixer_memento,
|
||||||
|
unsigned idx)
|
||||||
{
|
{
|
||||||
if (idx >= outputs.Size())
|
if (idx >= outputs.Size())
|
||||||
return false;
|
return false;
|
||||||
@@ -72,7 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
|||||||
auto *mixer = ao.GetMixer();
|
auto *mixer = ao.GetMixer();
|
||||||
if (mixer != nullptr) {
|
if (mixer != nullptr) {
|
||||||
mixer_close(mixer);
|
mixer_close(mixer);
|
||||||
InvalidateHardwareVolume();
|
mixer_memento.InvalidateHardwareVolume();
|
||||||
idle_add(IDLE_MIXER);
|
idle_add(IDLE_MIXER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +88,9 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
audio_output_toggle_index(MultipleOutputs &outputs,
|
||||||
|
MixerMemento &mixer_memento,
|
||||||
|
unsigned idx)
|
||||||
{
|
{
|
||||||
if (idx >= outputs.Size())
|
if (idx >= outputs.Size())
|
||||||
return false;
|
return false;
|
||||||
@@ -97,7 +103,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
|||||||
auto *mixer = ao.GetMixer();
|
auto *mixer = ao.GetMixer();
|
||||||
if (mixer != nullptr) {
|
if (mixer != nullptr) {
|
||||||
mixer_close(mixer);
|
mixer_close(mixer);
|
||||||
InvalidateHardwareVolume();
|
mixer_memento.InvalidateHardwareVolume();
|
||||||
idle_add(IDLE_MIXER);
|
idle_add(IDLE_MIXER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,26 +28,33 @@
|
|||||||
#define MPD_OUTPUT_COMMAND_HXX
|
#define MPD_OUTPUT_COMMAND_HXX
|
||||||
|
|
||||||
class MultipleOutputs;
|
class MultipleOutputs;
|
||||||
|
class MixerMemento;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables an audio output. Returns false if the specified output
|
* Enables an audio output. Returns false if the specified output
|
||||||
* does not exist.
|
* does not exist.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx);
|
audio_output_enable_index(MultipleOutputs &outputs,
|
||||||
|
MixerMemento &mixer_memento,
|
||||||
|
unsigned idx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables an audio output. Returns false if the specified output
|
* Disables an audio output. Returns false if the specified output
|
||||||
* does not exist.
|
* does not exist.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx);
|
audio_output_disable_index(MultipleOutputs &outputs,
|
||||||
|
MixerMemento &mixer_memento,
|
||||||
|
unsigned idx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles an audio output. Returns false if the specified output
|
* Toggles an audio output. Returns false if the specified output
|
||||||
* does not exist.
|
* does not exist.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
|
audio_output_toggle_index(MultipleOutputs &outputs,
|
||||||
|
MixerMemento &mixer_memento,
|
||||||
|
unsigned idx);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -47,6 +47,15 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
// Backward compatibility from OSX 12.0 API change
|
||||||
|
#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000)
|
||||||
|
#define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMain
|
||||||
|
#define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMainVolume
|
||||||
|
#else
|
||||||
|
#define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMaster
|
||||||
|
#define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMasterVolume
|
||||||
|
#endif
|
||||||
|
|
||||||
static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100;
|
static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100;
|
||||||
|
|
||||||
static StringBuffer<64>
|
static StringBuffer<64>
|
||||||
@@ -160,13 +169,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block)
|
|||||||
static constexpr AudioObjectPropertyAddress default_system_output_device{
|
static constexpr AudioObjectPropertyAddress default_system_output_device{
|
||||||
kAudioHardwarePropertyDefaultSystemOutputDevice,
|
kAudioHardwarePropertyDefaultSystemOutputDevice,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster,
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr AudioObjectPropertyAddress default_output_device{
|
static constexpr AudioObjectPropertyAddress default_output_device{
|
||||||
kAudioHardwarePropertyDefaultOutputDevice,
|
kAudioHardwarePropertyDefaultOutputDevice,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto &aopa =
|
const auto &aopa =
|
||||||
@@ -195,9 +204,9 @@ int
|
|||||||
OSXOutput::GetVolume()
|
OSXOutput::GetVolume()
|
||||||
{
|
{
|
||||||
static constexpr AudioObjectPropertyAddress aopa = {
|
static constexpr AudioObjectPropertyAddress aopa = {
|
||||||
kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster,
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
|
const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
|
||||||
@@ -211,9 +220,9 @@ OSXOutput::SetVolume(unsigned new_volume)
|
|||||||
{
|
{
|
||||||
Float32 vol = new_volume / 100.0;
|
Float32 vol = new_volume / 100.0;
|
||||||
static constexpr AudioObjectPropertyAddress aopa = {
|
static constexpr AudioObjectPropertyAddress aopa = {
|
||||||
kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||||
};
|
};
|
||||||
UInt32 size = sizeof(vol);
|
UInt32 size = sizeof(vol);
|
||||||
OSStatus status = AudioObjectSetPropertyData(dev_id,
|
OSStatus status = AudioObjectSetPropertyData(dev_id,
|
||||||
@@ -366,25 +375,25 @@ osx_output_set_device_format(AudioDeviceID dev_id,
|
|||||||
static constexpr AudioObjectPropertyAddress aopa_device_streams = {
|
static constexpr AudioObjectPropertyAddress aopa_device_streams = {
|
||||||
kAudioDevicePropertyStreams,
|
kAudioDevicePropertyStreams,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
|
static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
|
||||||
kAudioStreamPropertyDirection,
|
kAudioStreamPropertyDirection,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
|
static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
|
||||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
|
static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
|
||||||
kAudioStreamPropertyPhysicalFormat,
|
kAudioStreamPropertyPhysicalFormat,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||||
};
|
};
|
||||||
|
|
||||||
OSStatus err;
|
OSStatus err;
|
||||||
@@ -484,7 +493,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept
|
|||||||
static constexpr AudioObjectPropertyAddress aopa = {
|
static constexpr AudioObjectPropertyAddress aopa = {
|
||||||
kAudioDevicePropertyHogMode,
|
kAudioDevicePropertyHogMode,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioObjectPropertyElementMaster
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||||
};
|
};
|
||||||
|
|
||||||
pid_t hog_pid;
|
pid_t hog_pid;
|
||||||
@@ -538,7 +547,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
|
|||||||
static constexpr AudioObjectPropertyAddress aopa_name{
|
static constexpr AudioObjectPropertyAddress aopa_name{
|
||||||
kAudioObjectPropertyName,
|
kAudioObjectPropertyName,
|
||||||
kAudioObjectPropertyScopeGlobal,
|
kAudioObjectPropertyScopeGlobal,
|
||||||
kAudioObjectPropertyElementMaster,
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||||
};
|
};
|
||||||
|
|
||||||
char actual_name[256];
|
char actual_name[256];
|
||||||
@@ -561,7 +570,7 @@ FindAudioDeviceByName(const char *name)
|
|||||||
static constexpr AudioObjectPropertyAddress aopa_hw_devices{
|
static constexpr AudioObjectPropertyAddress aopa_hw_devices{
|
||||||
kAudioHardwarePropertyDevices,
|
kAudioHardwarePropertyDevices,
|
||||||
kAudioObjectPropertyScopeGlobal,
|
kAudioObjectPropertyScopeGlobal,
|
||||||
kAudioObjectPropertyElementMaster,
|
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto ids =
|
const auto ids =
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "../Error.hxx"
|
#include "../Error.hxx"
|
||||||
#include "mixer/plugins/PipeWireMixerPlugin.hxx"
|
#include "mixer/plugins/PipeWireMixerPlugin.hxx"
|
||||||
#include "pcm/Silence.hxx"
|
#include "pcm/Silence.hxx"
|
||||||
|
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
#include "util/BitReverse.hxx"
|
#include "util/BitReverse.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <numeric>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -85,7 +87,14 @@ class PipeWireOutput final : AudioOutput {
|
|||||||
|
|
||||||
uint32_t target_id = PW_ID_ANY;
|
uint32_t target_id = PW_ID_ANY;
|
||||||
|
|
||||||
float volume = 1.0;
|
/**
|
||||||
|
* The current volume level (0.0 .. 1.0).
|
||||||
|
*
|
||||||
|
* This get initialized to -1 which means "unknown", so
|
||||||
|
* restore_volume will not attempt to override PipeWire's
|
||||||
|
* initial volume level.
|
||||||
|
*/
|
||||||
|
float volume = -1;
|
||||||
|
|
||||||
PipeWireMixer *mixer = nullptr;
|
PipeWireMixer *mixer = nullptr;
|
||||||
unsigned channels;
|
unsigned channels;
|
||||||
@@ -217,26 +226,34 @@ private:
|
|||||||
o.Drained();
|
o.Drained();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlInfo(const struct pw_stream_control *control) noexcept {
|
void OnChannelVolumes(const struct pw_stream_control &control) noexcept {
|
||||||
float sum = 0;
|
if (control.n_values < 1)
|
||||||
unsigned c;
|
return;
|
||||||
for (c = 0; c < control->n_values; c++)
|
|
||||||
sum += control->values[c];
|
|
||||||
|
|
||||||
sum /= control->n_values;
|
float sum = std::accumulate(control.values,
|
||||||
|
control.values + control.n_values,
|
||||||
|
0.0f);
|
||||||
|
volume = std::cbrt(sum / control.n_values);
|
||||||
|
|
||||||
if (mixer != nullptr)
|
if (mixer != nullptr)
|
||||||
pipewire_mixer_on_change(*mixer, std::cbrt(sum));
|
pipewire_mixer_on_change(*mixer, volume);
|
||||||
|
|
||||||
pw_thread_loop_signal(thread_loop, false);
|
pw_thread_loop_signal(thread_loop, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ControlInfo(void *data,
|
void ControlInfo([[maybe_unused]] uint32_t id,
|
||||||
[[maybe_unused]] uint32_t id,
|
const struct pw_stream_control &control) noexcept {
|
||||||
|
switch (id) {
|
||||||
|
case SPA_PROP_channelVolumes:
|
||||||
|
OnChannelVolumes(control);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ControlInfo(void *data, uint32_t id,
|
||||||
const struct pw_stream_control *control) noexcept {
|
const struct pw_stream_control *control) noexcept {
|
||||||
auto &o = *(PipeWireOutput *)data;
|
auto &o = *(PipeWireOutput *)data;
|
||||||
if (StringIsEqual(control->name, "Channel Volumes"))
|
o.ControlInfo(id, *control);
|
||||||
o.ControlInfo(control);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
@@ -308,23 +325,39 @@ PipeWireOutput::PipeWireOutput(const ConfigBlock &block)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
/**
|
||||||
PipeWireOutput::SetVolume(float _volume)
|
* Throws on error.
|
||||||
|
*
|
||||||
|
* @param volume a volume level between 0.0 and 1.0
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
SetVolume(struct pw_stream &stream, unsigned channels, float volume)
|
||||||
{
|
{
|
||||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
float value[MAX_CHANNELS];
|
||||||
|
std::fill_n(value, channels, volume * volume * volume);
|
||||||
|
|
||||||
float newvol = _volume*_volume*_volume;
|
if (pw_stream_set_control(&stream,
|
||||||
|
SPA_PROP_channelVolumes, channels, value,
|
||||||
if (stream != nullptr && !restore_volume) {
|
|
||||||
float vol[MAX_CHANNELS];
|
|
||||||
std::fill_n(vol, channels, newvol);
|
|
||||||
|
|
||||||
if (pw_stream_set_control(stream,
|
|
||||||
SPA_PROP_channelVolumes, channels, vol,
|
|
||||||
0) != 0)
|
0) != 0)
|
||||||
throw std::runtime_error("pw_stream_set_control() failed");
|
throw std::runtime_error("pw_stream_set_control() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PipeWireOutput::SetVolume(float _volume)
|
||||||
|
{
|
||||||
|
if (thread_loop == nullptr) {
|
||||||
|
/* the mixer is open (because it is a "global" mixer),
|
||||||
|
but Enable() on this output has not yet been
|
||||||
|
called */
|
||||||
|
volume = _volume;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||||
|
|
||||||
|
if (stream != nullptr && !restore_volume)
|
||||||
|
::SetVolume(*stream, channels, _volume);
|
||||||
|
|
||||||
volume = _volume;
|
volume = _volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +514,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
|||||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||||
PW_KEY_MEDIA_ROLE, "Music",
|
PW_KEY_MEDIA_ROLE, "Music",
|
||||||
PW_KEY_APP_NAME, "Music Player Daemon",
|
PW_KEY_APP_NAME, "Music Player Daemon",
|
||||||
|
PW_KEY_APP_ICON_NAME, "mpd",
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
pw_properties_setf(props, PW_KEY_NODE_NAME, "mpd.%s", name);
|
pw_properties_setf(props, PW_KEY_NODE_NAME, "mpd.%s", name);
|
||||||
@@ -638,8 +672,17 @@ PipeWireOutput::ParamChanged([[maybe_unused]] uint32_t id,
|
|||||||
[[maybe_unused]] const struct spa_pod *param) noexcept
|
[[maybe_unused]] const struct spa_pod *param) noexcept
|
||||||
{
|
{
|
||||||
if (restore_volume) {
|
if (restore_volume) {
|
||||||
SetVolume(volume);
|
|
||||||
restore_volume = false;
|
restore_volume = false;
|
||||||
|
|
||||||
|
if (volume >= 0) {
|
||||||
|
try {
|
||||||
|
::SetVolume(*stream, channels, volume);
|
||||||
|
} catch (...) {
|
||||||
|
FmtError(pipewire_output_domain,
|
||||||
|
FMT_STRING("Failed to restore volume: {}"),
|
||||||
|
std::current_exception());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)
|
||||||
@@ -824,6 +867,17 @@ PipeWireOutput::Drain()
|
|||||||
{
|
{
|
||||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||||
|
|
||||||
|
if (drained)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
/* there is data in the ring_buffer, but the stream is
|
||||||
|
not yet active; activate it now to ensure it is
|
||||||
|
played before this method returns */
|
||||||
|
active = true;
|
||||||
|
pw_stream_set_active(stream, true);
|
||||||
|
}
|
||||||
|
|
||||||
drain_requested = true;
|
drain_requested = true;
|
||||||
AtScopeExit(this) { drain_requested = false; };
|
AtScopeExit(this) { drain_requested = false; };
|
||||||
|
|
||||||
@@ -839,7 +893,24 @@ PipeWireOutput::Cancel() noexcept
|
|||||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||||
interrupted = false;
|
interrupted = false;
|
||||||
|
|
||||||
|
if (drained)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* clear MPD's ring buffer */
|
||||||
ring_buffer->reset();
|
ring_buffer->reset();
|
||||||
|
|
||||||
|
/* clear libpipewire's buffer */
|
||||||
|
pw_stream_flush(stream, false);
|
||||||
|
drained = true;
|
||||||
|
|
||||||
|
/* pause the PipeWire stream so libpipewire ceases invoking
|
||||||
|
the "process" callback (we have no data until our Play()
|
||||||
|
method gets called again); the stream will be resume by
|
||||||
|
Play() after the ring_buffer has been refilled */
|
||||||
|
if (active) {
|
||||||
|
active = false;
|
||||||
|
pw_stream_set_active(stream, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class ShoutConfig {
|
|||||||
public:
|
public:
|
||||||
ShoutConfig(const ConfigBlock &block, const char *mime_type);
|
ShoutConfig(const ConfigBlock &block, const char *mime_type);
|
||||||
|
|
||||||
void Setup(shout_t *connection) const;
|
void Setup(shout_t &connection) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ShoutOutput final : AudioOutput {
|
struct ShoutOutput final : AudioOutput {
|
||||||
@@ -229,41 +229,56 @@ ShoutOutput::Create(EventLoop &, const ConfigBlock &block)
|
|||||||
return new ShoutOutput(block);
|
return new ShoutOutput(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
static void
|
||||||
ShoutConfig::Setup(shout_t *connection) const
|
SetMeta(shout_t &connection, const char *name, const char *value)
|
||||||
{
|
{
|
||||||
if (shout_set_host(connection, host) != SHOUTERR_SUCCESS ||
|
if (shout_set_meta(&connection, name, value) != SHOUTERR_SUCCESS)
|
||||||
shout_set_port(connection, port) != SHOUTERR_SUCCESS ||
|
throw std::runtime_error(shout_get_error(&connection));
|
||||||
shout_set_password(connection, passwd) != SHOUTERR_SUCCESS ||
|
}
|
||||||
shout_set_mount(connection, mount) != SHOUTERR_SUCCESS ||
|
|
||||||
shout_set_name(connection, name) != SHOUTERR_SUCCESS ||
|
static void
|
||||||
shout_set_user(connection, user) != SHOUTERR_SUCCESS ||
|
SetOptionalMeta(shout_t &connection, const char *name, const char *value)
|
||||||
shout_set_public(connection, is_public) != SHOUTERR_SUCCESS ||
|
{
|
||||||
shout_set_format(connection, format) != SHOUTERR_SUCCESS ||
|
if (value != nullptr)
|
||||||
shout_set_protocol(connection, protocol) != SHOUTERR_SUCCESS ||
|
SetMeta(connection, name, value);
|
||||||
#ifdef SHOUT_TLS
|
}
|
||||||
shout_set_tls(connection, tls) != SHOUTERR_SUCCESS ||
|
|
||||||
|
inline void
|
||||||
|
ShoutConfig::Setup(shout_t &connection) const
|
||||||
|
{
|
||||||
|
if (shout_set_host(&connection, host) != SHOUTERR_SUCCESS ||
|
||||||
|
shout_set_port(&connection, port) != SHOUTERR_SUCCESS ||
|
||||||
|
shout_set_password(&connection, passwd) != SHOUTERR_SUCCESS ||
|
||||||
|
shout_set_mount(&connection, mount) != SHOUTERR_SUCCESS ||
|
||||||
|
shout_set_user(&connection, user) != SHOUTERR_SUCCESS ||
|
||||||
|
shout_set_public(&connection, is_public) != SHOUTERR_SUCCESS ||
|
||||||
|
#ifdef SHOUT_USAGE_AUDIO
|
||||||
|
/* since libshout 2.4.3 */
|
||||||
|
shout_set_content_format(&connection, format, SHOUT_USAGE_AUDIO,
|
||||||
|
nullptr) != SHOUTERR_SUCCESS ||
|
||||||
|
#else
|
||||||
|
shout_set_format(&connection, format) != SHOUTERR_SUCCESS ||
|
||||||
#endif
|
#endif
|
||||||
shout_set_agent(connection, "MPD") != SHOUTERR_SUCCESS)
|
shout_set_protocol(&connection, protocol) != SHOUTERR_SUCCESS ||
|
||||||
throw std::runtime_error(shout_get_error(connection));
|
#ifdef SHOUT_TLS
|
||||||
|
shout_set_tls(&connection, tls) != SHOUTERR_SUCCESS ||
|
||||||
|
#endif
|
||||||
|
shout_set_agent(&connection, "MPD") != SHOUTERR_SUCCESS)
|
||||||
|
throw std::runtime_error(shout_get_error(&connection));
|
||||||
|
|
||||||
|
SetMeta(connection, SHOUT_META_NAME, name);
|
||||||
|
|
||||||
/* optional paramters */
|
/* optional paramters */
|
||||||
|
|
||||||
if (genre != nullptr && shout_set_genre(connection, genre))
|
SetOptionalMeta(connection, SHOUT_META_GENRE, genre);
|
||||||
throw std::runtime_error(shout_get_error(connection));
|
SetOptionalMeta(connection, SHOUT_META_DESCRIPTION, description);
|
||||||
|
SetOptionalMeta(connection, SHOUT_META_URL, url);
|
||||||
if (description != nullptr &&
|
|
||||||
shout_set_description(connection, description))
|
|
||||||
throw std::runtime_error(shout_get_error(connection));
|
|
||||||
|
|
||||||
if (url != nullptr && shout_set_url(connection, url))
|
|
||||||
throw std::runtime_error(shout_get_error(connection));
|
|
||||||
|
|
||||||
if (quality != nullptr)
|
if (quality != nullptr)
|
||||||
shout_set_audio_info(connection, SHOUT_AI_QUALITY, quality);
|
shout_set_audio_info(&connection, SHOUT_AI_QUALITY, quality);
|
||||||
|
|
||||||
if (bitrate != nullptr)
|
if (bitrate != nullptr)
|
||||||
shout_set_audio_info(connection, SHOUT_AI_BITRATE, bitrate);
|
shout_set_audio_info(&connection, SHOUT_AI_BITRATE, bitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -274,7 +289,7 @@ ShoutOutput::Enable()
|
|||||||
throw std::bad_alloc{};
|
throw std::bad_alloc{};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
config.Setup(shout_conn);
|
config.Setup(*shout_conn);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
shout_free(shout_conn);
|
shout_free(shout_conn);
|
||||||
throw;
|
throw;
|
||||||
@@ -418,7 +433,7 @@ ShoutOutput::Pause()
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
shout_tag_to_metadata(const Tag &tag, char *dest, size_t size)
|
shout_tag_to_metadata(const Tag &tag, char *dest, size_t size) noexcept
|
||||||
{
|
{
|
||||||
const char *artist = tag.GetValue(TAG_ARTIST);
|
const char *artist = tag.GetValue(TAG_ARTIST);
|
||||||
const char *title = tag.GetValue(TAG_TITLE);
|
const char *title = tag.GetValue(TAG_TITLE);
|
||||||
@@ -446,9 +461,15 @@ ShoutOutput::SendTag(const Tag &tag)
|
|||||||
char song[1024];
|
char song[1024];
|
||||||
shout_tag_to_metadata(tag, song, sizeof(song));
|
shout_tag_to_metadata(tag, song, sizeof(song));
|
||||||
|
|
||||||
shout_metadata_add(meta, "song", song);
|
if (SHOUTERR_SUCCESS != shout_metadata_add(meta, "song", song) ||
|
||||||
shout_metadata_add(meta, "charset", "UTF-8");
|
#ifdef SHOUT_FORMAT_TEXT
|
||||||
if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) {
|
/* since libshout 2.4.6 */
|
||||||
|
SHOUTERR_SUCCESS != shout_set_metadata_utf8(shout_conn, meta)
|
||||||
|
#else
|
||||||
|
SHOUTERR_SUCCESS != shout_metadata_add(meta, "charset", "UTF-8") ||
|
||||||
|
SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
LogWarning(shout_output_domain,
|
LogWarning(shout_output_domain,
|
||||||
"error setting shout metadata");
|
"error setting shout metadata");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ output_plugins_deps = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
need_encoder = false
|
need_encoder = false
|
||||||
|
need_wave_encoder = false
|
||||||
|
|
||||||
if alsa_dep.found()
|
if alsa_dep.found()
|
||||||
output_plugins_sources += 'AlsaOutputPlugin.cxx'
|
output_plugins_sources += 'AlsaOutputPlugin.cxx'
|
||||||
@@ -99,7 +100,7 @@ if get_option('recorder')
|
|||||||
need_encoder = true
|
need_encoder = true
|
||||||
endif
|
endif
|
||||||
|
|
||||||
libshout_dep = dependency('shout', required: get_option('shout'))
|
libshout_dep = dependency('shout', version: '>= 2.4.0', required: get_option('shout'))
|
||||||
output_features.set('HAVE_SHOUT', libshout_dep.found())
|
output_features.set('HAVE_SHOUT', libshout_dep.found())
|
||||||
if libshout_dep.found()
|
if libshout_dep.found()
|
||||||
output_plugins_sources += 'ShoutOutputPlugin.cxx'
|
output_plugins_sources += 'ShoutOutputPlugin.cxx'
|
||||||
@@ -127,9 +128,7 @@ if get_option('snapcast')
|
|||||||
|
|
||||||
output_features.set('HAVE_YAJL', yajl_dep.found())
|
output_features.set('HAVE_YAJL', yajl_dep.found())
|
||||||
|
|
||||||
# TODO: the Snapcast plugin needs just the "wave" encoder, but this
|
need_wave_encoder = true
|
||||||
# enables all available encoders
|
|
||||||
need_encoder = true
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
enable_solaris_output = get_option('solaris_output')
|
enable_solaris_output = get_option('solaris_output')
|
||||||
|
|||||||
@@ -214,9 +214,8 @@ SnapcastOutput::RemoveClient(SnapcastClient &client) noexcept
|
|||||||
std::chrono::steady_clock::duration
|
std::chrono::steady_clock::duration
|
||||||
SnapcastOutput::Delay() const noexcept
|
SnapcastOutput::Delay() const noexcept
|
||||||
{
|
{
|
||||||
if (!LockHasClients() && pause) {
|
if (pause) {
|
||||||
/* if there's no client and this output is paused,
|
/* Pause() will not do anything, it will not fill
|
||||||
then Pause() will not do anything, it will not fill
|
|
||||||
the buffer and it will not update the timer;
|
the buffer and it will not update the timer;
|
||||||
therefore, we reset the timer here */
|
therefore, we reset the timer here */
|
||||||
timer->Reset();
|
timer->Reset();
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ IsXmlContentType(const char *content_type) noexcept
|
|||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
static bool
|
static bool
|
||||||
IsXmlContentType(const std::multimap<std::string, std::string> &headers) noexcept
|
IsXmlContentType(const Curl::Headers &headers) noexcept
|
||||||
{
|
{
|
||||||
auto i = headers.find("content-type");
|
auto i = headers.find("content-type");
|
||||||
return i != headers.end() && IsXmlContentType(i->second.c_str());
|
return i != headers.end() && IsXmlContentType(i->second.c_str());
|
||||||
@@ -297,8 +297,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* virtual methods from CurlResponseHandler */
|
/* virtual methods from CurlResponseHandler */
|
||||||
void OnHeaders(unsigned status,
|
void OnHeaders(unsigned status, Curl::Headers &&headers) final {
|
||||||
std::multimap<std::string, std::string> &&headers) final {
|
|
||||||
if (status != 207)
|
if (status != 207)
|
||||||
throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
|
throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
|
||||||
status);
|
status);
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ try {
|
|||||||
unsigned n = FromLE32(footer.count);
|
unsigned n = FromLE32(footer.count);
|
||||||
const char *p = buffer.get();
|
const char *p = buffer.get();
|
||||||
while (n-- && remaining > 10) {
|
while (n-- && remaining > 10) {
|
||||||
size_t size = FromLE32(*(const uint32_t *)p);
|
size_t size = *(const PackedLE32 *)p;
|
||||||
p += 4;
|
p += 4;
|
||||||
remaining -= 4;
|
remaining -= 4;
|
||||||
unsigned long flags = FromLE32(*(const uint32_t *)p);
|
unsigned long flags = *(const PackedLE32 *)p;
|
||||||
p += 4;
|
p += 4;
|
||||||
remaining -= 4;
|
remaining -= 4;
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ ReadString(ConstBuffer<uint8_t> &src) noexcept
|
|||||||
if (src.size < 4)
|
if (src.size < 4)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
const size_t length = FromBE32(*(const uint32_t *)src.data);
|
const size_t length = *(const PackedBE32 *)src.data;
|
||||||
src.skip_front(4);
|
src.skip_front(4);
|
||||||
|
|
||||||
if (src.size < length)
|
if (src.size < length)
|
||||||
@@ -65,7 +65,7 @@ ScanId3Apic(ConstBuffer<void> _buffer, TagHandler &handler) noexcept
|
|||||||
|
|
||||||
buffer.skip_front(16);
|
buffer.skip_front(16);
|
||||||
|
|
||||||
const size_t image_size = FromBE32(*(const uint32_t *)buffer.data);
|
const size_t image_size = *(const PackedBE32 *)buffer.data;
|
||||||
buffer.skip_front(4);
|
buffer.skip_front(4);
|
||||||
|
|
||||||
if (buffer.size < image_size)
|
if (buffer.size < image_size)
|
||||||
|
|||||||
@@ -297,6 +297,60 @@ public:
|
|||||||
static_assert(sizeof(PackedBE16) == sizeof(uint16_t), "Wrong size");
|
static_assert(sizeof(PackedBE16) == sizeof(uint16_t), "Wrong size");
|
||||||
static_assert(alignof(PackedBE16) == 1, "Wrong alignment");
|
static_assert(alignof(PackedBE16) == 1, "Wrong alignment");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A packed big-endian 32 bit integer.
|
||||||
|
*/
|
||||||
|
class PackedBE32 {
|
||||||
|
uint8_t a, b, c, d;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PackedBE32() = default;
|
||||||
|
|
||||||
|
constexpr PackedBE32(uint32_t src) noexcept
|
||||||
|
:a(uint8_t(src >> 24)),
|
||||||
|
b(uint8_t(src >> 16)),
|
||||||
|
c(uint8_t(src >> 8)),
|
||||||
|
d(uint8_t(src)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an instance from an integer which is already
|
||||||
|
* big-endian.
|
||||||
|
*/
|
||||||
|
static constexpr auto FromBE(uint32_t src) noexcept {
|
||||||
|
union {
|
||||||
|
uint32_t in;
|
||||||
|
PackedBE32 out;
|
||||||
|
} u{src};
|
||||||
|
return u.out;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr operator uint32_t() const noexcept {
|
||||||
|
return (uint32_t(a) << 24) | (uint32_t(b) << 16) |
|
||||||
|
(uint32_t(c) << 8) | uint32_t(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedBE32 &operator=(uint32_t new_value) noexcept {
|
||||||
|
d = uint8_t(new_value);
|
||||||
|
c = uint8_t(new_value >> 8);
|
||||||
|
b = uint8_t(new_value >> 16);
|
||||||
|
a = uint8_t(new_value >> 24);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the raw, big-endian value.
|
||||||
|
*/
|
||||||
|
constexpr uint32_t raw() const noexcept {
|
||||||
|
uint32_t x = *this;
|
||||||
|
if (IsLittleEndian())
|
||||||
|
x = ByteSwap32(x);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(PackedBE32) == sizeof(uint32_t), "Wrong size");
|
||||||
|
static_assert(alignof(PackedBE32) == 1, "Wrong alignment");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A packed little-endian 16 bit integer.
|
* A packed little-endian 16 bit integer.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
[wrap-file]
|
[wrap-file]
|
||||||
directory = expat-2.2.9
|
directory = expat-2.4.8
|
||||||
source_url = https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.xz
|
source_url = https://github.com/libexpat/libexpat/releases/download/R_2_4_8/expat-2.4.8.tar.xz
|
||||||
source_filename = expat-2.2.9.tar.bz2
|
source_filename = expat-2.4.8.tar.bz2
|
||||||
source_hash = 1ea6965b15c2106b6bbe883397271c80dfa0331cdf821b2c319591b55eadc0a4
|
source_hash = f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25
|
||||||
patch_url = https://wrapdb.mesonbuild.com/v1/projects/expat/2.2.9/3/get_zip
|
patch_filename = expat_2.4.8-1_patch.zip
|
||||||
patch_filename = expat-2.2.9-3-wrap.zip
|
patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.4.8-1/get_patch
|
||||||
patch_hash = e9aaace62e9a158b5e96f5c38c9f81f369179206acd87697653d777c0d3975d3
|
patch_hash = 9aec253a2c6d1c0feb852c5c6920298d14701eeec7acc6832bb402438b52112a
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
expat = expat_dep
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[wrap-file]
|
[wrap-file]
|
||||||
directory = fmt-7.1.3
|
directory = fmt-8.1.1
|
||||||
source_url = https://github.com/fmtlib/fmt/archive/7.1.3.tar.gz
|
source_url = https://github.com/fmtlib/fmt/archive/8.1.1.tar.gz
|
||||||
source_filename = fmt-7.1.3.tar.gz
|
source_filename = fmt-8.1.1.tar.gz
|
||||||
source_hash = 5cae7072042b3043e12d53d50ef404bbb76949dad1de368d7f993a15c8c05ecc
|
source_hash = 3d794d3cf67633b34b2771eb9f073bde87e846e0d395d254df7b211ef1ec7346
|
||||||
patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/7.1.3/1/get_zip
|
patch_filename = fmt_8.1.1-2_patch.zip
|
||||||
patch_filename = fmt-7.1.3-1-wrap.zip
|
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_8.1.1-2/get_patch
|
||||||
patch_hash = 6eb951a51806fd6ffd596064825c39b844c1fe1799840ef507b61a53dba08213
|
patch_hash = cd001046281330a8862591780a9ea71a1fa594edd0d015deb24e44680c9ea33b
|
||||||
|
wrapdb_version = 8.1.1-2
|
||||||
|
|
||||||
[provide]
|
[provide]
|
||||||
fmt = fmt_dep
|
fmt = fmt_dep
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
[wrap-file]
|
[wrap-file]
|
||||||
directory = googletest-release-1.10.0
|
directory = googletest-release-1.11.0
|
||||||
|
source_url = https://github.com/google/googletest/archive/release-1.11.0.tar.gz
|
||||||
|
source_filename = gtest-1.11.0.tar.gz
|
||||||
|
source_hash = b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5
|
||||||
|
patch_filename = gtest_1.11.0-2_patch.zip
|
||||||
|
patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.11.0-2/get_patch
|
||||||
|
patch_hash = 764530d812ac161c9eab02a8cfaec67c871fcfc5548e29fd3d488070913d4e94
|
||||||
|
|
||||||
source_url = https://github.com/google/googletest/archive/release-1.10.0.zip
|
[provide]
|
||||||
source_filename = gtest-1.10.0.zip
|
gtest = gtest_dep
|
||||||
source_hash = 94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91
|
gtest_main = gtest_main_dep
|
||||||
|
gmock = gmock_dep
|
||||||
|
gmock_main = gmock_main_dep
|
||||||
|
|
||||||
patch_url = https://wrapdb.mesonbuild.com/v1/projects/gtest/1.10.0/1/get_zip
|
|
||||||
patch_filename = gtest-1.10.0-1-wrap.zip
|
|
||||||
patch_hash = 04ff14e8880e4e465f6260221e9dfd56fea6bc7cce4c4aff0dc528e4a2c8f514
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[wrap-file]
|
[wrap-file]
|
||||||
directory = sqlite-amalgamation-3340100
|
directory = sqlite-amalgamation-3380000
|
||||||
source_url = https://www.sqlite.org/2021/sqlite-amalgamation-3340100.zip
|
source_url = https://sqlite.org/2022/sqlite-amalgamation-3380000.zip
|
||||||
source_filename = sqlite-amalgamation-3340100.zip
|
source_filename = sqlite-amalgamation-3380000.zip
|
||||||
source_hash = e0b1c0345fe4338b936e17da8e1bd88366cd210e576834546977f040c12a8f68
|
source_hash = e055f6054e97747a135c89e36520c0a423249e8a91c5fc445163f4a6adb20df6
|
||||||
patch_url = https://wrapdb.mesonbuild.com/v1/projects/sqlite3/3.34.1/1/get_zip
|
patch_filename = sqlite3_3.38.0-1_patch.zip
|
||||||
patch_filename = sqlite3-3.34.1-1-wrap.zip
|
patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.38.0-1/get_patch
|
||||||
patch_hash = cba9e47bdb4c02f88fadaae8deab357218d32562c6b86ce7ba0c72f107044360
|
patch_hash = 49e30bf010ff63ab772d5417885e6905379025ceac80382e292c6dbd3a9da744
|
||||||
|
|
||||||
[provide]
|
[provide]
|
||||||
sqlite3 = sqlite3_dep
|
sqlite3 = sqlite3_dep
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user