Compare commits
	
		
			164 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8842650c33 | ||
| 
						 | 
					d5bf128cee | ||
| 
						 | 
					5cd86e272f | ||
| 
						 | 
					740cbe9e02 | ||
| 
						 | 
					ed890a273a | ||
| 
						 | 
					068cd559e1 | ||
| 
						 | 
					dc127f39a7 | ||
| 
						 | 
					7a99a7008c | ||
| 
						 | 
					70b451db7b | ||
| 
						 | 
					2ab03a0914 | ||
| 
						 | 
					2fa8c7d2db | ||
| 
						 | 
					7c759ba8b0 | ||
| 
						 | 
					6d9b452fde | ||
| 
						 | 
					f7eb1c9a83 | ||
| 
						 | 
					2d22e6dee4 | ||
| 
						 | 
					4587bf759d | ||
| 
						 | 
					e1e37cfe3c | ||
| 
						 | 
					381934985a | ||
| 
						 | 
					a8042885ac | ||
| 
						 | 
					a71e68db50 | ||
| 
						 | 
					1417578b3d | ||
| 
						 | 
					96befa138c | ||
| 
						 | 
					16a99804de | ||
| 
						 | 
					75a39ed279 | ||
| 
						 | 
					4d357ab77c | ||
| 
						 | 
					d4f3dd49b4 | ||
| 
						 | 
					4ec6d0555a | ||
| 
						 | 
					a6a1182c4c | ||
| 
						 | 
					a59c9c602b | ||
| 
						 | 
					0c4d824d64 | ||
| 
						 | 
					a5281856c9 | ||
| 
						 | 
					0206a46d39 | ||
| 
						 | 
					9475ef2202 | ||
| 
						 | 
					edae00e719 | ||
| 
						 | 
					fb695bc55f | ||
| 
						 | 
					23a5b8fd3c | ||
| 
						 | 
					273a93cfcf | ||
| 
						 | 
					d105985d78 | ||
| 
						 | 
					f8cfeb39e9 | ||
| 
						 | 
					d5d3982d3c | ||
| 
						 | 
					47341107ea | ||
| 
						 | 
					90eaa87a4d | ||
| 
						 | 
					b09a54b2c2 | ||
| 
						 | 
					10aec174d5 | ||
| 
						 | 
					d32ed194e8 | ||
| 
						 | 
					70d0fbd715 | ||
| 
						 | 
					302432e157 | ||
| 
						 | 
					4ab8a677dc | ||
| 
						 | 
					52e4a4c904 | ||
| 
						 | 
					a0f6932ebe | ||
| 
						 | 
					6e700dab69 | ||
| 
						 | 
					35eaed7206 | ||
| 
						 | 
					e7c963f2ce | ||
| 
						 | 
					949d72e368 | ||
| 
						 | 
					8d2a184658 | ||
| 
						 | 
					c877a32d97 | ||
| 
						 | 
					541468f0ca | ||
| 
						 | 
					d2797effa3 | ||
| 
						 | 
					1170fb1e1e | ||
| 
						 | 
					65b9b3195c | ||
| 
						 | 
					258830e913 | ||
| 
						 | 
					d91da96798 | ||
| 
						 | 
					b3897df682 | ||
| 
						 | 
					3cacb56bb7 | ||
| 
						 | 
					15a1973e28 | ||
| 
						 | 
					ad7d47a8ba | ||
| 
						 | 
					0948c607b6 | ||
| 
						 | 
					60d04052c5 | ||
| 
						 | 
					c1780ac657 | ||
| 
						 | 
					e49cf0ec38 | ||
| 
						 | 
					e1d641f684 | ||
| 
						 | 
					4efd0a9f77 | ||
| 
						 | 
					f6f8751332 | ||
| 
						 | 
					abb28593ce | ||
| 
						 | 
					115693b046 | ||
| 
						 | 
					e4b055eb6d | ||
| 
						 | 
					9866adff95 | ||
| 
						 | 
					a8b0c55818 | ||
| 
						 | 
					cac88e8be5 | ||
| 
						 | 
					e9f6a3482c | ||
| 
						 | 
					5d2e80f188 | ||
| 
						 | 
					cfd4d5b13e | ||
| 
						 | 
					06514aec63 | ||
| 
						 | 
					4ded1ae67b | ||
| 
						 | 
					1da974e3fa | ||
| 
						 | 
					94f06f0946 | ||
| 
						 | 
					d9eec8a455 | ||
| 
						 | 
					eaecbcafb2 | ||
| 
						 | 
					73b5d0a9b9 | ||
| 
						 | 
					c2d0f35e7a | ||
| 
						 | 
					ab99a57997 | ||
| 
						 | 
					c8ebaf3521 | ||
| 
						 | 
					52d00f7e30 | ||
| 
						 | 
					309491a6d8 | ||
| 
						 | 
					e7bfd32ccc | ||
| 
						 | 
					6f283b52ab | ||
| 
						 | 
					32bddfabea | ||
| 
						 | 
					1944c826bc | ||
| 
						 | 
					619bb60b26 | ||
| 
						 | 
					c549e16ed1 | ||
| 
						 | 
					01c9c4507f | ||
| 
						 | 
					8c9d7bf07e | ||
| 
						 | 
					44ef34db88 | ||
| 
						 | 
					5781f223f6 | ||
| 
						 | 
					e4c8ebe056 | ||
| 
						 | 
					76b25a1377 | ||
| 
						 | 
					ccc3ee663b | ||
| 
						 | 
					0626661764 | ||
| 
						 | 
					31db04a3ca | ||
| 
						 | 
					0c7163b9db | ||
| 
						 | 
					7d78cad8af | ||
| 
						 | 
					912530ed20 | ||
| 
						 | 
					d3f37199b9 | ||
| 
						 | 
					a4748d84b0 | ||
| 
						 | 
					8f847ec381 | ||
| 
						 | 
					3a70f09dd3 | ||
| 
						 | 
					568f63100b | ||
| 
						 | 
					3e25916b37 | ||
| 
						 | 
					5f9438dae6 | ||
| 
						 | 
					99e65c58ce | ||
| 
						 | 
					df71b07e9d | ||
| 
						 | 
					2694195215 | ||
| 
						 | 
					66450d1f3c | ||
| 
						 | 
					76efea3aa7 | ||
| 
						 | 
					7ab0dfc8ce | ||
| 
						 | 
					15ff7c4cad | ||
| 
						 | 
					9ab9b97f20 | ||
| 
						 | 
					88d92aceab | ||
| 
						 | 
					a2ce4352c8 | ||
| 
						 | 
					84f43ccde8 | ||
| 
						 | 
					38704c9cf3 | ||
| 
						 | 
					910d0ec92b | ||
| 
						 | 
					3b05c89765 | ||
| 
						 | 
					e77b3fa46f | ||
| 
						 | 
					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 | 
							
								
								
									
										68
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								NEWS
									
									
									
									
									
								
							@@ -1,3 +1,71 @@
 | 
				
			|||||||
 | 
					ver 0.23.13 (2023/05/22)
 | 
				
			||||||
 | 
					* input
 | 
				
			||||||
 | 
					  - curl: fix busy loop after connection failed
 | 
				
			||||||
 | 
					  - curl: hide "404" log messages for non-existent ".mpdignore" files
 | 
				
			||||||
 | 
					* archive
 | 
				
			||||||
 | 
					  - zzip: fix crash bug
 | 
				
			||||||
 | 
					* database
 | 
				
			||||||
 | 
					  - simple: reveal hidden songs after deleting containing CUE
 | 
				
			||||||
 | 
					* decoder
 | 
				
			||||||
 | 
					  - ffmpeg: reorder to a lower priority than "gme"
 | 
				
			||||||
 | 
					  - gme: require GME 0.6 or later
 | 
				
			||||||
 | 
					* output
 | 
				
			||||||
 | 
					  - pipewire: fix corruption bug due to missing lock
 | 
				
			||||||
 | 
					* Linux
 | 
				
			||||||
 | 
					  - shut down if parent process dies in --no-daemon mode
 | 
				
			||||||
 | 
					  - determine systemd unit directories via pkg-config
 | 
				
			||||||
 | 
					* support libfmt 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ver 0.23.12 (2023/01/17)
 | 
				
			||||||
 | 
					* input
 | 
				
			||||||
 | 
					  - curl: require CURL 7.55.0 or later
 | 
				
			||||||
 | 
					* decoder
 | 
				
			||||||
 | 
					  - mad: fix integer underflow with very small files
 | 
				
			||||||
 | 
					* tags
 | 
				
			||||||
 | 
					  - fix crash bug due to race condition
 | 
				
			||||||
 | 
					* output
 | 
				
			||||||
 | 
					  - pipewire: adjust to PipeWire 0.3.64 API change
 | 
				
			||||||
 | 
					* fix build failures with GCC 13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ver 0.23.11 (2022/11/28)
 | 
				
			||||||
 | 
					* database
 | 
				
			||||||
 | 
					  - simple: move default database to ~/.cache/mpd/db from ~/.cache/mpd.db
 | 
				
			||||||
 | 
					  - simple: default "cache_directory" to ~/.cache/mpd/mounts
 | 
				
			||||||
 | 
					* macOS: fix build failure "no archive members specified"
 | 
				
			||||||
 | 
					* Windows
 | 
				
			||||||
 | 
					  - fix crash bug (stack buffer overflow) after I/O errors
 | 
				
			||||||
 | 
					  - fix path traversal bug because backslash was allowed in playlist names
 | 
				
			||||||
 | 
					* Android/Windows
 | 
				
			||||||
 | 
					  - update OpenSSL to 3.0.7
 | 
				
			||||||
 | 
					  - re-enable CURL's verbose error strings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ver 0.23.10 (2022/10/14)
 | 
				
			||||||
 | 
					* storage
 | 
				
			||||||
 | 
					  - curl: fix file time stamps
 | 
				
			||||||
 | 
					* decoder
 | 
				
			||||||
 | 
					  - ffmpeg: fix libfmt 9 compiler warning
 | 
				
			||||||
 | 
					* encoder
 | 
				
			||||||
 | 
					  - flac: fix failure when libFLAC is built without Ogg support
 | 
				
			||||||
 | 
					* output
 | 
				
			||||||
 | 
					  - alsa: fix crash bug
 | 
				
			||||||
 | 
					* Windows
 | 
				
			||||||
 | 
					  - log to stdout by default, don't require "log_file" setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
					ver 0.23.8 (2022/07/09)
 | 
				
			||||||
* storage
 | 
					* storage
 | 
				
			||||||
  - curl: fix crash if web server does not understand WebDAV
 | 
					  - curl: fix crash if web server does not understand WebDAV
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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="66"
 | 
					          android:versionCode="71"
 | 
				
			||||||
          android:versionName="0.23.7">
 | 
					          android:versionName="0.23.12">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <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" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
@@ -100,6 +100,7 @@ class AndroidNdkToolchain:
 | 
				
			|||||||
        common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
 | 
					        common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.ar = os.path.join(llvm_bin, 'llvm-ar')
 | 
					        self.ar = os.path.join(llvm_bin, 'llvm-ar')
 | 
				
			||||||
 | 
					        self.arflags = 'rcs'
 | 
				
			||||||
        self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
 | 
					        self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
 | 
				
			||||||
        self.nm = os.path.join(llvm_bin, 'llvm-nm')
 | 
					        self.nm = os.path.join(llvm_bin, 'llvm-nm')
 | 
				
			||||||
        self.strip = os.path.join(llvm_bin, 'llvm-strip')
 | 
					        self.strip = os.path.join(llvm_bin, 'llvm-strip')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,10 @@ author = 'Max Kellermann'
 | 
				
			|||||||
# built documents.
 | 
					# built documents.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# The short X.Y version.
 | 
					# The short X.Y version.
 | 
				
			||||||
version = '0.23.8'
 | 
					with open('../meson.build') as f:
 | 
				
			||||||
 | 
					    import re
 | 
				
			||||||
 | 
					    version = re.match(r"project\([^\)]*\bversion:\s*'([^']+)'",
 | 
				
			||||||
 | 
					                       f.read(4096)).group(1)
 | 
				
			||||||
# The full version, including alpha/beta/rc tags.
 | 
					# The full version, including alpha/beta/rc tags.
 | 
				
			||||||
#release = version + '~git'
 | 
					#release = version + '~git'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,7 +50,7 @@ version = '0.23.8'
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
# This is also used if you do content translation via gettext catalogs.
 | 
					# This is also used if you do content translation via gettext catalogs.
 | 
				
			||||||
# Usually you set "language" from the command line for these cases.
 | 
					# Usually you set "language" from the command line for these cases.
 | 
				
			||||||
language = None
 | 
					language = "en"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# There are two options for replacing |today|: either, you set today to some
 | 
					# There are two options for replacing |today|: either, you set today to some
 | 
				
			||||||
# non-false value, then it is used:
 | 
					# non-false value, then it is used:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,12 @@ Music Player Daemon
 | 
				
			|||||||
   client
 | 
					   client
 | 
				
			||||||
   protocol
 | 
					   protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. toctree::
 | 
				
			||||||
 | 
					   :maxdepth: 1
 | 
				
			||||||
 | 
					   :caption: man pages:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   mpd.1
 | 
				
			||||||
 | 
					   mpd.conf.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Indices and tables
 | 
					Indices and tables
 | 
				
			||||||
==================
 | 
					==================
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -181,7 +181,7 @@
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
#database {
 | 
					#database {
 | 
				
			||||||
#       plugin "simple"
 | 
					#       plugin "simple"
 | 
				
			||||||
#       path "~/.local/share/mpd/db
 | 
					#       path "~/.local/share/mpd/db"
 | 
				
			||||||
#       cache_directory "~/.local/share/mpd/cache"
 | 
					#       cache_directory "~/.local/share/mpd/cache"
 | 
				
			||||||
#}
 | 
					#}
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
----
 | 
					----
 | 
				
			||||||
@@ -214,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Note that unless overridden by the below settings (e.g. by setting
 | 
					Note that unless overridden by the below settings (e.g. by setting
 | 
				
			||||||
them to a blank value), general curl configuration from environment
 | 
					them to a blank value), general curl configuration from environment
 | 
				
			||||||
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
 | 
					variables such as ``http_proxy`` will be in effect.
 | 
				
			||||||
will be in effect.
 | 
					
 | 
				
			||||||
 | 
					User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. list-table::
 | 
					.. list-table::
 | 
				
			||||||
   :widths: 20 80
 | 
					   :widths: 20 80
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								doc/user.rst
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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.
 | 
				
			||||||
@@ -197,7 +199,7 @@ Compiling for Android
 | 
				
			|||||||
You need:
 | 
					You need:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Android SDK
 | 
					* Android SDK
 | 
				
			||||||
* `Android NDK r23 <https://developer.android.com/ndk/downloads>`_
 | 
					* `Android NDK r25b <https://developer.android.com/ndk/downloads>`_
 | 
				
			||||||
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
 | 
					* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
 | 
				
			||||||
  <https://ninja-build.org/>`__
 | 
					  <https://ninja-build.org/>`__
 | 
				
			||||||
* cmake
 | 
					* cmake
 | 
				
			||||||
@@ -609,6 +611,11 @@ If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
 | 
				
			|||||||
set to a value (in dB) between ``-15`` and ``15``.  This is the gain
 | 
					set to a value (in dB) between ``-15`` and ``15``.  This is the gain
 | 
				
			||||||
applied to songs with ReplayGain tags.
 | 
					applied to songs with ReplayGain tags.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					On songs without ReplayGain tags, the setting
 | 
				
			||||||
 | 
					``replaygain_missing_preamp`` is used instead.  If this setting is not
 | 
				
			||||||
 | 
					configured, then no ReplayGain is applied to such songs, and they will
 | 
				
			||||||
 | 
					appear too loud.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ReplayGain is usually implemented with a software volume filter (which
 | 
					ReplayGain is usually implemented with a software volume filter (which
 | 
				
			||||||
prevents `Bit-perfect playback`_).  To use a hardware mixer, set
 | 
					prevents `Bit-perfect playback`_).  To use a hardware mixer, set
 | 
				
			||||||
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
 | 
					``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								meson.build
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
				
			|||||||
project(
 | 
					project(
 | 
				
			||||||
  'mpd',
 | 
					  'mpd',
 | 
				
			||||||
  ['c', 'cpp'],
 | 
					  ['c', 'cpp'],
 | 
				
			||||||
  version: '0.23.8',
 | 
					  version: '0.23.13',
 | 
				
			||||||
  meson_version: '>= 0.56.0',
 | 
					  meson_version: '>= 0.56.0',
 | 
				
			||||||
  default_options: [
 | 
					  default_options: [
 | 
				
			||||||
    'c_std=c11',
 | 
					    'c_std=c11',
 | 
				
			||||||
@@ -205,7 +205,6 @@ enable_daemon = not is_windows and not is_android and get_option('daemon')
 | 
				
			|||||||
conf.set('ENABLE_DAEMON', enable_daemon)
 | 
					conf.set('ENABLE_DAEMON', enable_daemon)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
 | 
					conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
 | 
				
			||||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
 | 
					 | 
				
			||||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
 | 
					conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
 | 
				
			||||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
 | 
					conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -251,6 +250,14 @@ endif
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
 | 
					fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if compiler.get_id() == 'clang' and compiler.version().version_compare('<15')
 | 
				
			||||||
 | 
					  fmt_dep = declare_dependency(
 | 
				
			||||||
 | 
					    dependencies: fmt_dep,
 | 
				
			||||||
 | 
					    # suppress bogus clang 14 warning (the version in Android NDK r25b)
 | 
				
			||||||
 | 
					    compile_args: ['-Wno-unused-local-typedef'],
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log = static_library(
 | 
					log = static_library(
 | 
				
			||||||
  'log',
 | 
					  'log',
 | 
				
			||||||
  'src/Log.cxx',
 | 
					  'src/Log.cxx',
 | 
				
			||||||
@@ -352,7 +359,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',
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -382,6 +389,7 @@ endif
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
if enable_database
 | 
					if enable_database
 | 
				
			||||||
  sources += [
 | 
					  sources += [
 | 
				
			||||||
 | 
					    'src/storage/StorageState.cxx',
 | 
				
			||||||
    'src/queue/PlaylistUpdate.cxx',
 | 
					    'src/queue/PlaylistUpdate.cxx',
 | 
				
			||||||
    'src/command/StorageCommands.cxx',
 | 
					    'src/command/StorageCommands.cxx',
 | 
				
			||||||
    'src/command/DatabaseCommands.cxx',
 | 
					    'src/command/DatabaseCommands.cxx',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,14 +45,27 @@ class AutotoolsProject(MakeProject):
 | 
				
			|||||||
            'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
 | 
					            'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
 | 
				
			||||||
            'LIBS=' + toolchain.libs + ' ' + self.libs,
 | 
					            'LIBS=' + toolchain.libs + ' ' + self.libs,
 | 
				
			||||||
            'AR=' + toolchain.ar,
 | 
					            'AR=' + toolchain.ar,
 | 
				
			||||||
 | 
					            'ARFLAGS=' + toolchain.arflags,
 | 
				
			||||||
            'RANLIB=' + toolchain.ranlib,
 | 
					            'RANLIB=' + toolchain.ranlib,
 | 
				
			||||||
            'STRIP=' + toolchain.strip,
 | 
					            'STRIP=' + toolchain.strip,
 | 
				
			||||||
            '--host=' + toolchain.arch,
 | 
					            '--host=' + toolchain.arch,
 | 
				
			||||||
            '--prefix=' + toolchain.install_prefix,
 | 
					            '--prefix=' + toolchain.install_prefix,
 | 
				
			||||||
            '--enable-silent-rules',
 | 
					            '--disable-silent-rules',
 | 
				
			||||||
        ] + self.configure_args
 | 
					        ] + self.configure_args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        subprocess.check_call(configure, cwd=build, env=toolchain.env)
 | 
					        try:
 | 
				
			||||||
 | 
					            print(configure)
 | 
				
			||||||
 | 
					            subprocess.check_call(configure, cwd=build, env=toolchain.env)
 | 
				
			||||||
 | 
					        except subprocess.CalledProcessError:
 | 
				
			||||||
 | 
					            # dump config.log after a failed configure run
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                with open(os.path.join(build, 'config.log')) as f:
 | 
				
			||||||
 | 
					                    sys.stdout.write(f.read())
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					            # re-raise the exception
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return build
 | 
					        return build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _build(self, toolchain):
 | 
					    def _build(self, toolchain):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from build.project import Project
 | 
					from build.project import Project
 | 
				
			||||||
@@ -25,13 +26,33 @@ set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]})
 | 
				
			|||||||
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
 | 
					set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
 | 
				
			||||||
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
 | 
					set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}")
 | 
					set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
 | 
				
			||||||
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}")
 | 
					set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
 | 
				
			||||||
""")
 | 
					""")
 | 
				
			||||||
    __write_cmake_compiler(f, 'C', toolchain.cc)
 | 
					    __write_cmake_compiler(f, 'C', toolchain.cc)
 | 
				
			||||||
    __write_cmake_compiler(f, 'CXX', toolchain.cxx)
 | 
					    __write_cmake_compiler(f, 'CXX', toolchain.cxx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def configure(toolchain, src, build, args=()):
 | 
					    if cmake_system_name == 'Darwin':
 | 
				
			||||||
 | 
					        # On macOS, cmake forcibly adds an "-isysroot" flag even if
 | 
				
			||||||
 | 
					        # one is already present in the flags variable; this breaks
 | 
				
			||||||
 | 
					        # cross-compiling for iOS, and can be worked around by setting
 | 
				
			||||||
 | 
					        # the CMAKE_OSX_SYSROOT variable
 | 
				
			||||||
 | 
					        # (https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html).
 | 
				
			||||||
 | 
					        m = re.search(r'-isysroot +(\S+)', toolchain.cflags)
 | 
				
			||||||
 | 
					        if m:
 | 
				
			||||||
 | 
					            sysroot = m.group(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            print(f'set(CMAKE_OSX_SYSROOT {sysroot})', file=f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # search libraries and headers only in the sysroot, not on
 | 
				
			||||||
 | 
					            # the build host
 | 
				
			||||||
 | 
					            f.write(f"""
 | 
				
			||||||
 | 
					set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
 | 
				
			||||||
 | 
					set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
 | 
				
			||||||
 | 
					set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
 | 
				
			||||||
 | 
					""")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def configure(toolchain, src, build, args=(), env=None):
 | 
				
			||||||
    cross_args = []
 | 
					    cross_args = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if toolchain.is_windows:
 | 
					    if toolchain.is_windows:
 | 
				
			||||||
@@ -61,15 +82,23 @@ def configure(toolchain, src, build, args=()):
 | 
				
			|||||||
        '-GNinja',
 | 
					        '-GNinja',
 | 
				
			||||||
    ] + cross_args + args
 | 
					    ] + cross_args + args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subprocess.check_call(configure, env=toolchain.env, cwd=build)
 | 
					    if env is None:
 | 
				
			||||||
 | 
					        env = toolchain.env
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        env = {**toolchain.env, **env}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print(configure)
 | 
				
			||||||
 | 
					    subprocess.check_call(configure, env=env, cwd=build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CmakeProject(Project):
 | 
					class CmakeProject(Project):
 | 
				
			||||||
    def __init__(self, url, md5, installed, configure_args=[],
 | 
					    def __init__(self, url, md5, installed, configure_args=[],
 | 
				
			||||||
                 windows_configure_args=[],
 | 
					                 windows_configure_args=[],
 | 
				
			||||||
 | 
					                 env=None,
 | 
				
			||||||
                 **kwargs):
 | 
					                 **kwargs):
 | 
				
			||||||
        Project.__init__(self, url, md5, installed, **kwargs)
 | 
					        Project.__init__(self, url, md5, installed, **kwargs)
 | 
				
			||||||
        self.configure_args = configure_args
 | 
					        self.configure_args = configure_args
 | 
				
			||||||
        self.windows_configure_args = windows_configure_args
 | 
					        self.windows_configure_args = windows_configure_args
 | 
				
			||||||
 | 
					        self.env = env
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def configure(self, toolchain):
 | 
					    def configure(self, toolchain):
 | 
				
			||||||
        src = self.unpack(toolchain)
 | 
					        src = self.unpack(toolchain)
 | 
				
			||||||
@@ -77,10 +106,10 @@ class CmakeProject(Project):
 | 
				
			|||||||
        configure_args = self.configure_args
 | 
					        configure_args = self.configure_args
 | 
				
			||||||
        if toolchain.is_windows:
 | 
					        if toolchain.is_windows:
 | 
				
			||||||
            configure_args = configure_args + self.windows_configure_args
 | 
					            configure_args = configure_args + self.windows_configure_args
 | 
				
			||||||
        configure(toolchain, src, build, configure_args)
 | 
					        configure(toolchain, src, build, configure_args, self.env)
 | 
				
			||||||
        return build
 | 
					        return build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _build(self, toolchain):
 | 
					    def _build(self, toolchain):
 | 
				
			||||||
        build = self.configure(toolchain)
 | 
					        build = self.configure(toolchain)
 | 
				
			||||||
        subprocess.check_call(['ninja', 'install'],
 | 
					        subprocess.check_call(['ninja', '-v', 'install'],
 | 
				
			||||||
                              cwd=build, env=toolchain.env)
 | 
					                              cwd=build, env=toolchain.env)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,20 +43,22 @@ opus = AutotoolsProject(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
flac = AutotoolsProject(
 | 
					flac = AutotoolsProject(
 | 
				
			||||||
    'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
 | 
					    'http://downloads.xiph.org/releases/flac/flac-1.4.2.tar.xz',
 | 
				
			||||||
    '8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
 | 
					    'e322d58a1f48d23d9dd38f432672865f6f79e73a6f9cc5a5f57fcaa83eb5a8e4',
 | 
				
			||||||
    'lib/libFLAC.a',
 | 
					    'lib/libFLAC.a',
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        '--disable-shared', '--enable-static',
 | 
					        '--disable-shared', '--enable-static',
 | 
				
			||||||
 | 
					        '--disable-stack-smash-protection',
 | 
				
			||||||
        '--disable-xmms-plugin', '--disable-cpplibs',
 | 
					        '--disable-xmms-plugin', '--disable-cpplibs',
 | 
				
			||||||
        '--disable-doxygen-docs',
 | 
					        '--disable-doxygen-docs',
 | 
				
			||||||
 | 
					        '--disable-programs',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    subdirs=['include', 'src/libFLAC'],
 | 
					    subdirs=['include', 'src/libFLAC'],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
zlib = ZlibProject(
 | 
					zlib = ZlibProject(
 | 
				
			||||||
    'http://zlib.net/zlib-1.2.12.tar.xz',
 | 
					    'http://zlib.net/zlib-1.2.13.tar.xz',
 | 
				
			||||||
    '7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18',
 | 
					    'd14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98',
 | 
				
			||||||
    'lib/libz.a',
 | 
					    'lib/libz.a',
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -112,30 +114,31 @@ libmodplug = AutotoolsProject(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
libopenmpt = AutotoolsProject(
 | 
					libopenmpt = AutotoolsProject(
 | 
				
			||||||
    'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
 | 
					    'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
 | 
				
			||||||
    '892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
 | 
					    '6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
 | 
				
			||||||
    'lib/libopenmpt.a',
 | 
					    'lib/libopenmpt.a',
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        '--disable-shared', '--enable-static',
 | 
					        '--disable-shared', '--enable-static',
 | 
				
			||||||
        '--disable-openmpt123',
 | 
					        '--disable-openmpt123',
 | 
				
			||||||
 | 
					        '--disable-examples',
 | 
				
			||||||
 | 
					        '--disable-tests',
 | 
				
			||||||
 | 
					        '--disable-doxygen-doc',
 | 
				
			||||||
        '--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
 | 
					        '--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
 | 
				
			||||||
        '--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
 | 
					        '--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
 | 
				
			||||||
 | 
					        '--without-flac',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    base='libopenmpt-0.5.12+release.autotools',
 | 
					    base='libopenmpt-0.6.6+release.autotools',
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wildmidi = CmakeProject(
 | 
					wildmidi = CmakeProject(
 | 
				
			||||||
    'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4',
 | 
					    'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz',
 | 
				
			||||||
    '6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430',
 | 
					    'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99',
 | 
				
			||||||
    'lib/libWildMidi.a',
 | 
					    'lib/libWildMidi.a',
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        '-DBUILD_SHARED_LIBS=OFF',
 | 
					        '-DBUILD_SHARED_LIBS=OFF',
 | 
				
			||||||
        '-DWANT_PLAYER=OFF',
 | 
					        '-DWANT_PLAYER=OFF',
 | 
				
			||||||
        '-DWANT_STATIC=ON',
 | 
					        '-DWANT_STATIC=ON',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    base='wildmidi-wildmidi-0.4.4',
 | 
					 | 
				
			||||||
    name='wildmidi',
 | 
					 | 
				
			||||||
    version='0.4.4',
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gme = CmakeProject(
 | 
					gme = CmakeProject(
 | 
				
			||||||
@@ -151,8 +154,8 @@ gme = CmakeProject(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ffmpeg = FfmpegProject(
 | 
					ffmpeg = FfmpegProject(
 | 
				
			||||||
    'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz',
 | 
					    'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz',
 | 
				
			||||||
    'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b',
 | 
					    '57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082',
 | 
				
			||||||
    'lib/libavcodec.a',
 | 
					    'lib/libavcodec.a',
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        '--disable-shared', '--enable-static',
 | 
					        '--disable-shared', '--enable-static',
 | 
				
			||||||
@@ -166,18 +169,20 @@ 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',
 | 
				
			||||||
        '--disable-encoders',
 | 
					        '--disable-encoders',
 | 
				
			||||||
 | 
					        '--disable-hwaccels',
 | 
				
			||||||
        '--disable-muxers',
 | 
					        '--disable-muxers',
 | 
				
			||||||
        '--disable-protocols',
 | 
					        '--disable-protocols',
 | 
				
			||||||
        '--disable-devices',
 | 
					        '--disable-devices',
 | 
				
			||||||
        '--disable-filters',
 | 
					        '--disable-filters',
 | 
				
			||||||
        '--disable-v4l2_m2m',
 | 
					        '--disable-v4l2_m2m',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '--disable-sdl2',
 | 
				
			||||||
        '--disable-vulkan',
 | 
					        '--disable-vulkan',
 | 
				
			||||||
 | 
					        '--disable-xlib',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '--disable-parser=bmp',
 | 
					        '--disable-parser=bmp',
 | 
				
			||||||
        '--disable-parser=cavsvideo',
 | 
					        '--disable-parser=cavsvideo',
 | 
				
			||||||
@@ -191,17 +196,22 @@ ffmpeg = FfmpegProject(
 | 
				
			|||||||
        '--disable-parser=h263',
 | 
					        '--disable-parser=h263',
 | 
				
			||||||
        '--disable-parser=h264',
 | 
					        '--disable-parser=h264',
 | 
				
			||||||
        '--disable-parser=hevc',
 | 
					        '--disable-parser=hevc',
 | 
				
			||||||
 | 
					        '--disable-parser=jpeg2000',
 | 
				
			||||||
        '--disable-parser=mjpeg',
 | 
					        '--disable-parser=mjpeg',
 | 
				
			||||||
        '--disable-parser=mlp',
 | 
					        '--disable-parser=mlp',
 | 
				
			||||||
        '--disable-parser=mpeg4video',
 | 
					        '--disable-parser=mpeg4video',
 | 
				
			||||||
        '--disable-parser=mpegvideo',
 | 
					        '--disable-parser=mpegvideo',
 | 
				
			||||||
        '--disable-parser=opus',
 | 
					        '--disable-parser=opus',
 | 
				
			||||||
 | 
					        '--disable-parser=qoi',
 | 
				
			||||||
 | 
					        '--disable-parser=rv30',
 | 
				
			||||||
 | 
					        '--disable-parser=rv40',
 | 
				
			||||||
        '--disable-parser=vc1',
 | 
					        '--disable-parser=vc1',
 | 
				
			||||||
        '--disable-parser=vp3',
 | 
					        '--disable-parser=vp3',
 | 
				
			||||||
        '--disable-parser=vp8',
 | 
					        '--disable-parser=vp8',
 | 
				
			||||||
        '--disable-parser=vp9',
 | 
					        '--disable-parser=vp9',
 | 
				
			||||||
        '--disable-parser=png',
 | 
					        '--disable-parser=png',
 | 
				
			||||||
        '--disable-parser=pnm',
 | 
					        '--disable-parser=pnm',
 | 
				
			||||||
 | 
					        '--disable-parser=webp',
 | 
				
			||||||
        '--disable-parser=xma',
 | 
					        '--disable-parser=xma',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '--disable-demuxer=aqtitle',
 | 
					        '--disable-demuxer=aqtitle',
 | 
				
			||||||
@@ -217,6 +227,42 @@ ffmpeg = FfmpegProject(
 | 
				
			|||||||
        '--disable-demuxer=h264',
 | 
					        '--disable-demuxer=h264',
 | 
				
			||||||
        '--disable-demuxer=ico',
 | 
					        '--disable-demuxer=ico',
 | 
				
			||||||
        '--disable-demuxer=image2',
 | 
					        '--disable-demuxer=image2',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image2pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_bmp_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_cri_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_dds_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_dpx_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_exr_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_gem_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_gif_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_j2k_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_jpeg_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_jpegls_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_jpegxl_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pam_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pbm_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pcx_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pfm_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pgm_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pgmyuv_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pgx_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_phm_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_photocd_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_pictor_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_png_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_ppm_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_psd_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_qdraw_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_qoi_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_sgi_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_sunrast_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_svg_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_tiff_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_vbn_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_webp_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_xbm_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_xpm_pipe',
 | 
				
			||||||
 | 
					        '--disable-demuxer=image_xwd_pipe',
 | 
				
			||||||
        '--disable-demuxer=jacosub',
 | 
					        '--disable-demuxer=jacosub',
 | 
				
			||||||
        '--disable-demuxer=lrc',
 | 
					        '--disable-demuxer=lrc',
 | 
				
			||||||
        '--disable-demuxer=microdvd',
 | 
					        '--disable-demuxer=microdvd',
 | 
				
			||||||
@@ -239,6 +285,7 @@ ffmpeg = FfmpegProject(
 | 
				
			|||||||
        '--disable-demuxer=tedcaptions',
 | 
					        '--disable-demuxer=tedcaptions',
 | 
				
			||||||
        '--disable-demuxer=vobsub',
 | 
					        '--disable-demuxer=vobsub',
 | 
				
			||||||
        '--disable-demuxer=vplayer',
 | 
					        '--disable-demuxer=vplayer',
 | 
				
			||||||
 | 
					        '--disable-demuxer=webm_dash_manifest',
 | 
				
			||||||
        '--disable-demuxer=webvtt',
 | 
					        '--disable-demuxer=webvtt',
 | 
				
			||||||
        '--disable-demuxer=yuv4mpegpipe',
 | 
					        '--disable-demuxer=yuv4mpegpipe',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -268,78 +315,179 @@ ffmpeg = FfmpegProject(
 | 
				
			|||||||
        '--disable-decoder=qdmc',
 | 
					        '--disable-decoder=qdmc',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # disable lots of image and video codecs
 | 
					        # disable lots of image and video codecs
 | 
				
			||||||
 | 
					        '--disable-decoder=acelp_kelvin',
 | 
				
			||||||
 | 
					        '--disable-decoder=agm',
 | 
				
			||||||
 | 
					        '--disable-decoder=aic',
 | 
				
			||||||
 | 
					        '--disable-decoder=alias_pix',
 | 
				
			||||||
 | 
					        '--disable-decoder=ansi',
 | 
				
			||||||
 | 
					        '--disable-decoder=apng',
 | 
				
			||||||
 | 
					        '--disable-decoder=arbc',
 | 
				
			||||||
 | 
					        '--disable-decoder=argo',
 | 
				
			||||||
        '--disable-decoder=ass',
 | 
					        '--disable-decoder=ass',
 | 
				
			||||||
        '--disable-decoder=asv1',
 | 
					        '--disable-decoder=asv1',
 | 
				
			||||||
        '--disable-decoder=asv2',
 | 
					        '--disable-decoder=asv2',
 | 
				
			||||||
        '--disable-decoder=apng',
 | 
					        '--disable-decoder=aura',
 | 
				
			||||||
 | 
					        '--disable-decoder=aura2',
 | 
				
			||||||
        '--disable-decoder=avrn',
 | 
					        '--disable-decoder=avrn',
 | 
				
			||||||
        '--disable-decoder=avrp',
 | 
					        '--disable-decoder=avrp',
 | 
				
			||||||
 | 
					        '--disable-decoder=avui',
 | 
				
			||||||
 | 
					        '--disable-decoder=ayuv',
 | 
				
			||||||
        '--disable-decoder=bethsoftvid',
 | 
					        '--disable-decoder=bethsoftvid',
 | 
				
			||||||
 | 
					        '--disable-decoder=bfi',
 | 
				
			||||||
        '--disable-decoder=bink',
 | 
					        '--disable-decoder=bink',
 | 
				
			||||||
 | 
					        '--disable-decoder=bintext',
 | 
				
			||||||
 | 
					        '--disable-decoder=bitpacked',
 | 
				
			||||||
        '--disable-decoder=bmp',
 | 
					        '--disable-decoder=bmp',
 | 
				
			||||||
        '--disable-decoder=bmv_video',
 | 
					        '--disable-decoder=bmv_video',
 | 
				
			||||||
 | 
					        '--disable-decoder=brender_pix',
 | 
				
			||||||
 | 
					        '--disable-decoder=c93',
 | 
				
			||||||
        '--disable-decoder=cavs',
 | 
					        '--disable-decoder=cavs',
 | 
				
			||||||
        '--disable-decoder=ccaption',
 | 
					        '--disable-decoder=ccaption',
 | 
				
			||||||
        '--disable-decoder=cdgraphics',
 | 
					        '--disable-decoder=cdgraphics',
 | 
				
			||||||
 | 
					        '--disable-decoder=cdtoons',
 | 
				
			||||||
 | 
					        '--disable-decoder=cdxl',
 | 
				
			||||||
 | 
					        '--disable-decoder=cfhd',
 | 
				
			||||||
 | 
					        '--disable-decoder=cinepak',
 | 
				
			||||||
        '--disable-decoder=clearvideo',
 | 
					        '--disable-decoder=clearvideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=cljr',
 | 
				
			||||||
 | 
					        '--disable-decoder=cllc',
 | 
				
			||||||
 | 
					        '--disable-decoder=cpia',
 | 
				
			||||||
 | 
					        '--disable-decoder=cscd',
 | 
				
			||||||
 | 
					        '--disable-decoder=cyuv',
 | 
				
			||||||
 | 
					        '--disable-decoder=dds',
 | 
				
			||||||
        '--disable-decoder=dirac',
 | 
					        '--disable-decoder=dirac',
 | 
				
			||||||
 | 
					        '--disable-decoder=dnxhd',
 | 
				
			||||||
 | 
					        '--disable-decoder=dpx',
 | 
				
			||||||
        '--disable-decoder=dsicinvideo',
 | 
					        '--disable-decoder=dsicinvideo',
 | 
				
			||||||
        '--disable-decoder=dvbsub',
 | 
					        '--disable-decoder=dvbsub',
 | 
				
			||||||
        '--disable-decoder=dvdsub',
 | 
					        '--disable-decoder=dvdsub',
 | 
				
			||||||
        '--disable-decoder=dvvideo',
 | 
					        '--disable-decoder=dvvideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=dxa',
 | 
				
			||||||
 | 
					        '--disable-decoder=dxtory',
 | 
				
			||||||
 | 
					        '--disable-decoder=dxv',
 | 
				
			||||||
 | 
					        '--disable-decoder=eacmv',
 | 
				
			||||||
 | 
					        '--disable-decoder=eamad',
 | 
				
			||||||
 | 
					        '--disable-decoder=eatgq',
 | 
				
			||||||
 | 
					        '--disable-decoder=eatgv',
 | 
				
			||||||
 | 
					        '--disable-decoder=eatqi',
 | 
				
			||||||
 | 
					        '--disable-decoder=eightbps',
 | 
				
			||||||
 | 
					        '--disable-decoder=escape124',
 | 
				
			||||||
 | 
					        '--disable-decoder=escape130',
 | 
				
			||||||
        '--disable-decoder=exr',
 | 
					        '--disable-decoder=exr',
 | 
				
			||||||
        '--disable-decoder=ffv1',
 | 
					        '--disable-decoder=ffv1',
 | 
				
			||||||
        '--disable-decoder=ffvhuff',
 | 
					        '--disable-decoder=ffvhuff',
 | 
				
			||||||
        '--disable-decoder=ffwavesynth',
 | 
					        '--disable-decoder=ffwavesynth',
 | 
				
			||||||
 | 
					        '--disable-decoder=fic',
 | 
				
			||||||
 | 
					        '--disable-decoder=fits',
 | 
				
			||||||
 | 
					        '--disable-decoder=flashsv',
 | 
				
			||||||
 | 
					        '--disable-decoder=flashsv2',
 | 
				
			||||||
        '--disable-decoder=flic',
 | 
					        '--disable-decoder=flic',
 | 
				
			||||||
        '--disable-decoder=flv',
 | 
					        '--disable-decoder=flv',
 | 
				
			||||||
 | 
					        '--disable-decoder=fmvc',
 | 
				
			||||||
        '--disable-decoder=fraps',
 | 
					        '--disable-decoder=fraps',
 | 
				
			||||||
 | 
					        '--disable-decoder=fourxm',
 | 
				
			||||||
 | 
					        '--disable-decoder=frwu',
 | 
				
			||||||
 | 
					        '--disable-decoder=g2m',
 | 
				
			||||||
 | 
					        '--disable-decoder=gdv',
 | 
				
			||||||
 | 
					        '--disable-decoder=gem',
 | 
				
			||||||
        '--disable-decoder=gif',
 | 
					        '--disable-decoder=gif',
 | 
				
			||||||
        '--disable-decoder=h261',
 | 
					        '--disable-decoder=h261',
 | 
				
			||||||
        '--disable-decoder=h263',
 | 
					        '--disable-decoder=h263',
 | 
				
			||||||
        '--disable-decoder=h263i',
 | 
					        '--disable-decoder=h263i',
 | 
				
			||||||
        '--disable-decoder=h263p',
 | 
					        '--disable-decoder=h263p',
 | 
				
			||||||
        '--disable-decoder=h264',
 | 
					        '--disable-decoder=h264',
 | 
				
			||||||
 | 
					        '--disable-decoder=hap',
 | 
				
			||||||
        '--disable-decoder=hevc',
 | 
					        '--disable-decoder=hevc',
 | 
				
			||||||
        '--disable-decoder=hnm4_video',
 | 
					        '--disable-decoder=hnm4_video',
 | 
				
			||||||
        '--disable-decoder=hq_hqa',
 | 
					        '--disable-decoder=hq_hqa',
 | 
				
			||||||
        '--disable-decoder=hqx',
 | 
					        '--disable-decoder=hqx',
 | 
				
			||||||
 | 
					        '--disable-decoder=huffyuv',
 | 
				
			||||||
 | 
					        '--disable-decoder=hymt',
 | 
				
			||||||
        '--disable-decoder=idcin',
 | 
					        '--disable-decoder=idcin',
 | 
				
			||||||
 | 
					        '--disable-decoder=idf',
 | 
				
			||||||
        '--disable-decoder=iff_ilbm',
 | 
					        '--disable-decoder=iff_ilbm',
 | 
				
			||||||
 | 
					        '--disable-decoder=imm4',
 | 
				
			||||||
        '--disable-decoder=indeo2',
 | 
					        '--disable-decoder=indeo2',
 | 
				
			||||||
        '--disable-decoder=indeo3',
 | 
					        '--disable-decoder=indeo3',
 | 
				
			||||||
        '--disable-decoder=indeo4',
 | 
					        '--disable-decoder=indeo4',
 | 
				
			||||||
        '--disable-decoder=indeo5',
 | 
					        '--disable-decoder=indeo5',
 | 
				
			||||||
        '--disable-decoder=interplay_video',
 | 
					        '--disable-decoder=interplay_video',
 | 
				
			||||||
 | 
					        '--disable-decoder=ipu',
 | 
				
			||||||
        '--disable-decoder=jacosub',
 | 
					        '--disable-decoder=jacosub',
 | 
				
			||||||
        '--disable-decoder=jpeg2000',
 | 
					        '--disable-decoder=jpeg2000',
 | 
				
			||||||
        '--disable-decoder=jpegls',
 | 
					        '--disable-decoder=jpegls',
 | 
				
			||||||
 | 
					        '--disable-decoder=jv',
 | 
				
			||||||
 | 
					        '--disable-decoder=kgv1',
 | 
				
			||||||
 | 
					        '--disable-decoder=kmvc',
 | 
				
			||||||
 | 
					        '--disable-decoder=lagarith',
 | 
				
			||||||
 | 
					        '--disable-decoder=loco',
 | 
				
			||||||
 | 
					        '--disable-decoder=lscr',
 | 
				
			||||||
 | 
					        '--disable-decoder=m101',
 | 
				
			||||||
 | 
					        '--disable-decoder=magicyuv',
 | 
				
			||||||
 | 
					        '--disable-decoder=mdec',
 | 
				
			||||||
        '--disable-decoder=microdvd',
 | 
					        '--disable-decoder=microdvd',
 | 
				
			||||||
        '--disable-decoder=mimic',
 | 
					        '--disable-decoder=mimic',
 | 
				
			||||||
        '--disable-decoder=mjpeg',
 | 
					        '--disable-decoder=mjpeg',
 | 
				
			||||||
        '--disable-decoder=mmvideo',
 | 
					        '--disable-decoder=mmvideo',
 | 
				
			||||||
        '--disable-decoder=mpl2',
 | 
					        '--disable-decoder=mpl2',
 | 
				
			||||||
 | 
					        '--disable-decoder=mobiclip',
 | 
				
			||||||
        '--disable-decoder=motionpixels',
 | 
					        '--disable-decoder=motionpixels',
 | 
				
			||||||
 | 
					        '--disable-decoder=movtext',
 | 
				
			||||||
        '--disable-decoder=mpeg1video',
 | 
					        '--disable-decoder=mpeg1video',
 | 
				
			||||||
        '--disable-decoder=mpeg2video',
 | 
					        '--disable-decoder=mpeg2video',
 | 
				
			||||||
        '--disable-decoder=mpeg4',
 | 
					        '--disable-decoder=mpeg4',
 | 
				
			||||||
        '--disable-decoder=mpegvideo',
 | 
					        '--disable-decoder=mpegvideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=msa1',
 | 
				
			||||||
        '--disable-decoder=mscc',
 | 
					        '--disable-decoder=mscc',
 | 
				
			||||||
        '--disable-decoder=msmpeg4_crystalhd',
 | 
					        '--disable-decoder=msmpeg4_crystalhd',
 | 
				
			||||||
        '--disable-decoder=msmpeg4v1',
 | 
					        '--disable-decoder=msmpeg4v1',
 | 
				
			||||||
        '--disable-decoder=msmpeg4v2',
 | 
					        '--disable-decoder=msmpeg4v2',
 | 
				
			||||||
        '--disable-decoder=msmpeg4v3',
 | 
					        '--disable-decoder=msmpeg4v3',
 | 
				
			||||||
 | 
					        '--disable-decoder=msp2',
 | 
				
			||||||
 | 
					        '--disable-decoder=msrle',
 | 
				
			||||||
 | 
					        '--disable-decoder=mss1',
 | 
				
			||||||
        '--disable-decoder=msvideo1',
 | 
					        '--disable-decoder=msvideo1',
 | 
				
			||||||
        '--disable-decoder=mszh',
 | 
					        '--disable-decoder=mszh',
 | 
				
			||||||
 | 
					        '--disable-decoder=mts2',
 | 
				
			||||||
 | 
					        '--disable-decoder=mv30',
 | 
				
			||||||
        '--disable-decoder=mvc1',
 | 
					        '--disable-decoder=mvc1',
 | 
				
			||||||
        '--disable-decoder=mvc2',
 | 
					        '--disable-decoder=mvc2',
 | 
				
			||||||
 | 
					        '--disable-decoder=mvdv',
 | 
				
			||||||
 | 
					        '--disable-decoder=mvha',
 | 
				
			||||||
 | 
					        '--disable-decoder=mwsc',
 | 
				
			||||||
 | 
					        '--disable-decoder=notchlc',
 | 
				
			||||||
 | 
					        '--disable-decoder=nuv',
 | 
				
			||||||
        '--disable-decoder=on2avc',
 | 
					        '--disable-decoder=on2avc',
 | 
				
			||||||
        '--disable-decoder=paf_video',
 | 
					        '--disable-decoder=paf_video',
 | 
				
			||||||
 | 
					        '--disable-decoder=pam',
 | 
				
			||||||
 | 
					        '--disable-decoder=pbm',
 | 
				
			||||||
 | 
					        '--disable-decoder=pcx',
 | 
				
			||||||
 | 
					        '--disable-decoder=pgm',
 | 
				
			||||||
 | 
					        '--disable-decoder=pgmyuv',
 | 
				
			||||||
 | 
					        '--disable-decoder=pgssub',
 | 
				
			||||||
 | 
					        '--disable-decoder=pgx',
 | 
				
			||||||
 | 
					        '--disable-decoder=phm',
 | 
				
			||||||
 | 
					        '--disable-decoder=photocd',
 | 
				
			||||||
        '--disable-decoder=png',
 | 
					        '--disable-decoder=png',
 | 
				
			||||||
 | 
					        '--disable-decoder=pictor',
 | 
				
			||||||
 | 
					        '--disable-decoder=pixlet',
 | 
				
			||||||
 | 
					        '--disable-decoder=pjs',
 | 
				
			||||||
 | 
					        '--disable-decoder=ppm',
 | 
				
			||||||
 | 
					        '--disable-decoder=prores',
 | 
				
			||||||
 | 
					        '--disable-decoder=prosumer',
 | 
				
			||||||
 | 
					        '--disable-decoder=psd',
 | 
				
			||||||
 | 
					        '--disable-decoder=ptx',
 | 
				
			||||||
        '--disable-decoder=qdraw',
 | 
					        '--disable-decoder=qdraw',
 | 
				
			||||||
 | 
					        '--disable-decoder=qoi',
 | 
				
			||||||
        '--disable-decoder=qpeg',
 | 
					        '--disable-decoder=qpeg',
 | 
				
			||||||
 | 
					        '--disable-decoder=qtrle',
 | 
				
			||||||
        '--disable-decoder=rawvideo',
 | 
					        '--disable-decoder=rawvideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=r10k',
 | 
				
			||||||
 | 
					        '--disable-decoder=r210',
 | 
				
			||||||
 | 
					        '--disable-decoder=rasc',
 | 
				
			||||||
        '--disable-decoder=realtext',
 | 
					        '--disable-decoder=realtext',
 | 
				
			||||||
 | 
					        '--disable-decoder=rl2',
 | 
				
			||||||
 | 
					        '--disable-decoder=rpza',
 | 
				
			||||||
        '--disable-decoder=roq',
 | 
					        '--disable-decoder=roq',
 | 
				
			||||||
        '--disable-decoder=roq_dpcm',
 | 
					        '--disable-decoder=roq_dpcm',
 | 
				
			||||||
        '--disable-decoder=rscc',
 | 
					        '--disable-decoder=rscc',
 | 
				
			||||||
@@ -348,53 +496,120 @@ ffmpeg = FfmpegProject(
 | 
				
			|||||||
        '--disable-decoder=rv30',
 | 
					        '--disable-decoder=rv30',
 | 
				
			||||||
        '--disable-decoder=rv40',
 | 
					        '--disable-decoder=rv40',
 | 
				
			||||||
        '--disable-decoder=sami',
 | 
					        '--disable-decoder=sami',
 | 
				
			||||||
 | 
					        '--disable-decoder=sanm',
 | 
				
			||||||
 | 
					        '--disable-decoder=scpr',
 | 
				
			||||||
 | 
					        '--disable-decoder=screenpresso',
 | 
				
			||||||
 | 
					        '--disable-decoder=sga',
 | 
				
			||||||
 | 
					        '--disable-decoder=sgi',
 | 
				
			||||||
 | 
					        '--disable-decoder=sgirle',
 | 
				
			||||||
        '--disable-decoder=sheervideo',
 | 
					        '--disable-decoder=sheervideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=simbiosis_imx',
 | 
				
			||||||
 | 
					        '--disable-decoder=smc',
 | 
				
			||||||
        '--disable-decoder=snow',
 | 
					        '--disable-decoder=snow',
 | 
				
			||||||
 | 
					        '--disable-decoder=speedhq',
 | 
				
			||||||
 | 
					        '--disable-decoder=srgc',
 | 
				
			||||||
        '--disable-decoder=srt',
 | 
					        '--disable-decoder=srt',
 | 
				
			||||||
 | 
					        '--disable-decoder=ssa',
 | 
				
			||||||
        '--disable-decoder=stl',
 | 
					        '--disable-decoder=stl',
 | 
				
			||||||
        '--disable-decoder=subrip',
 | 
					        '--disable-decoder=subrip',
 | 
				
			||||||
        '--disable-decoder=subviewer',
 | 
					        '--disable-decoder=subviewer',
 | 
				
			||||||
        '--disable-decoder=subviewer1',
 | 
					        '--disable-decoder=subviewer1',
 | 
				
			||||||
 | 
					        '--disable-decoder=sunrast',
 | 
				
			||||||
        '--disable-decoder=svq1',
 | 
					        '--disable-decoder=svq1',
 | 
				
			||||||
        '--disable-decoder=svq3',
 | 
					        '--disable-decoder=svq3',
 | 
				
			||||||
 | 
					        '--disable-decoder=targa',
 | 
				
			||||||
 | 
					        '--disable-decoder=targa_y216',
 | 
				
			||||||
 | 
					        '--disable-decoder=text',
 | 
				
			||||||
        '--disable-decoder=tiff',
 | 
					        '--disable-decoder=tiff',
 | 
				
			||||||
        '--disable-decoder=tiertexseqvideo',
 | 
					        '--disable-decoder=tiertexseqvideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=tmv',
 | 
				
			||||||
        '--disable-decoder=truemotion1',
 | 
					        '--disable-decoder=truemotion1',
 | 
				
			||||||
        '--disable-decoder=truemotion2',
 | 
					        '--disable-decoder=truemotion2',
 | 
				
			||||||
        '--disable-decoder=truemotion2rt',
 | 
					        '--disable-decoder=truemotion2rt',
 | 
				
			||||||
 | 
					        '--disable-decoder=tscc',
 | 
				
			||||||
 | 
					        '--disable-decoder=tscc2',
 | 
				
			||||||
        '--disable-decoder=twinvq',
 | 
					        '--disable-decoder=twinvq',
 | 
				
			||||||
 | 
					        '--disable-decoder=txd',
 | 
				
			||||||
 | 
					        '--disable-decoder=ulti',
 | 
				
			||||||
        '--disable-decoder=utvideo',
 | 
					        '--disable-decoder=utvideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=v210',
 | 
				
			||||||
 | 
					        '--disable-decoder=v210x',
 | 
				
			||||||
 | 
					        '--disable-decoder=v308',
 | 
				
			||||||
 | 
					        '--disable-decoder=v408',
 | 
				
			||||||
 | 
					        '--disable-decoder=v410',
 | 
				
			||||||
 | 
					        '--disable-decoder=vb',
 | 
				
			||||||
 | 
					        '--disable-decoder=vble',
 | 
				
			||||||
 | 
					        '--disable-decoder=vbn',
 | 
				
			||||||
        '--disable-decoder=vc1',
 | 
					        '--disable-decoder=vc1',
 | 
				
			||||||
 | 
					        '--disable-decoder=vcr1',
 | 
				
			||||||
        '--disable-decoder=vmdvideo',
 | 
					        '--disable-decoder=vmdvideo',
 | 
				
			||||||
 | 
					        '--disable-decoder=vmnc',
 | 
				
			||||||
        '--disable-decoder=vp3',
 | 
					        '--disable-decoder=vp3',
 | 
				
			||||||
        '--disable-decoder=vp5',
 | 
					        '--disable-decoder=vp5',
 | 
				
			||||||
        '--disable-decoder=vp6',
 | 
					        '--disable-decoder=vp6',
 | 
				
			||||||
        '--disable-decoder=vp7',
 | 
					        '--disable-decoder=vp7',
 | 
				
			||||||
        '--disable-decoder=vp8',
 | 
					        '--disable-decoder=vp8',
 | 
				
			||||||
        '--disable-decoder=vp9',
 | 
					        '--disable-decoder=vp9',
 | 
				
			||||||
 | 
					        '--disable-decoder=vplayer',
 | 
				
			||||||
        '--disable-decoder=vqa',
 | 
					        '--disable-decoder=vqa',
 | 
				
			||||||
        '--disable-decoder=webvtt',
 | 
					        '--disable-decoder=webvtt',
 | 
				
			||||||
 | 
					        '--disable-decoder=wcmv',
 | 
				
			||||||
        '--disable-decoder=wmv1',
 | 
					        '--disable-decoder=wmv1',
 | 
				
			||||||
        '--disable-decoder=wmv2',
 | 
					        '--disable-decoder=wmv2',
 | 
				
			||||||
        '--disable-decoder=wmv3',
 | 
					        '--disable-decoder=wmv3',
 | 
				
			||||||
 | 
					        '--disable-decoder=wnv1',
 | 
				
			||||||
 | 
					        '--disable-decoder=wrapped_avframe',
 | 
				
			||||||
 | 
					        '--disable-decoder=xan_wc3',
 | 
				
			||||||
 | 
					        '--disable-decoder=xan_wc4',
 | 
				
			||||||
 | 
					        '--disable-decoder=xbin',
 | 
				
			||||||
 | 
					        '--disable-decoder=xbm',
 | 
				
			||||||
 | 
					        '--disable-decoder=xface',
 | 
				
			||||||
 | 
					        '--disable-decoder=xl',
 | 
				
			||||||
 | 
					        '--disable-decoder=xpm',
 | 
				
			||||||
 | 
					        '--disable-decoder=xsub',
 | 
				
			||||||
 | 
					        '--disable-decoder=xwd',
 | 
				
			||||||
 | 
					        '--disable-decoder=y41p',
 | 
				
			||||||
 | 
					        '--disable-decoder=ylc',
 | 
				
			||||||
 | 
					        '--disable-decoder=yop',
 | 
				
			||||||
        '--disable-decoder=yuv4',
 | 
					        '--disable-decoder=yuv4',
 | 
				
			||||||
 | 
					        '--disable-decoder=zero12v',
 | 
				
			||||||
 | 
					        '--disable-decoder=zerocodec',
 | 
				
			||||||
 | 
					        '--disable-decoder=zlib',
 | 
				
			||||||
 | 
					        '--disable-decoder=zmbv',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '--disable-bsf=av1_frame_merge',
 | 
				
			||||||
 | 
					        '--disable-bsf=av1_frame_split',
 | 
				
			||||||
 | 
					        '--disable-bsf=av1_metadata',
 | 
				
			||||||
 | 
					        '--disable-bsf=dts2pts',
 | 
				
			||||||
 | 
					        '--disable-bsf=h264_metadata',
 | 
				
			||||||
 | 
					        '--disable-bsf=h264_mp4toannexb',
 | 
				
			||||||
 | 
					        '--disable-bsf=h264_redundant_pps',
 | 
				
			||||||
 | 
					        '--disable-bsf=hevc_metadata',
 | 
				
			||||||
 | 
					        '--disable-bsf=hevc_mp4toannexb',
 | 
				
			||||||
 | 
					        '--disable-bsf=mjpeg2jpeg',
 | 
				
			||||||
 | 
					        '--disable-bsf=opus_metadata',
 | 
				
			||||||
 | 
					        '--disable-bsf=pgs_frame_merge',
 | 
				
			||||||
 | 
					        '--disable-bsf=text2movsub',
 | 
				
			||||||
 | 
					        '--disable-bsf=vp9_metadata',
 | 
				
			||||||
 | 
					        '--disable-bsf=vp9_raw_reorder',
 | 
				
			||||||
 | 
					        '--disable-bsf=vp9_superframe',
 | 
				
			||||||
 | 
					        '--disable-bsf=vp9_superframe_split',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
openssl = OpenSSLProject(
 | 
					openssl = OpenSSLProject(
 | 
				
			||||||
    'https://www.openssl.org/source/openssl-3.0.5.tar.gz',
 | 
					    'https://www.openssl.org/source/openssl-3.1.0.tar.gz',
 | 
				
			||||||
    'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a',
 | 
					    'aaa925ad9828745c4cad9d9efeb273deca820f2cdcf2c3ac7d7c1212b7c497b4',
 | 
				
			||||||
    'include/openssl/ossl_typ.h',
 | 
					    'include/openssl/ossl_typ.h',
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
curl = CmakeProject(
 | 
					curl = CmakeProject(
 | 
				
			||||||
    'https://curl.se/download/curl-7.84.0.tar.xz',
 | 
					    'https://curl.se/download/curl-8.0.1.tar.xz',
 | 
				
			||||||
    '2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8',
 | 
					    '0a381cd82f4d00a9a334438b8ca239afea5bfefcfa9a1025f2bf118e79e0b5f0',
 | 
				
			||||||
    'lib/libcurl.a',
 | 
					    'lib/libcurl.a',
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        '-DBUILD_CURL_EXE=OFF',
 | 
					        '-DBUILD_CURL_EXE=OFF',
 | 
				
			||||||
        '-DBUILD_SHARED_LIBS=OFF',
 | 
					        '-DBUILD_SHARED_LIBS=OFF',
 | 
				
			||||||
        '-DCURL_DISABLE_VERBOSE_STRINGS=ON',
 | 
					 | 
				
			||||||
        '-DCURL_DISABLE_LDAP=ON',
 | 
					        '-DCURL_DISABLE_LDAP=ON',
 | 
				
			||||||
        '-DCURL_DISABLE_TELNET=ON',
 | 
					        '-DCURL_DISABLE_TELNET=ON',
 | 
				
			||||||
        '-DCURL_DISABLE_DICT=ON',
 | 
					        '-DCURL_DISABLE_DICT=ON',
 | 
				
			||||||
@@ -423,8 +638,8 @@ curl = CmakeProject(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
libnfs = AutotoolsProject(
 | 
					libnfs = AutotoolsProject(
 | 
				
			||||||
    'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
 | 
					    'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
 | 
				
			||||||
    '7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
 | 
					    '637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
 | 
				
			||||||
    'lib/libnfs.a',
 | 
					    'lib/libnfs.a',
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        '--disable-shared', '--enable-static',
 | 
					        '--disable-shared', '--enable-static',
 | 
				
			||||||
@@ -435,7 +650,7 @@ libnfs = AutotoolsProject(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        '--disable-utils', '--disable-examples',
 | 
					        '--disable-utils', '--disable-examples',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    base='libnfs-libnfs-5.0.1',
 | 
					    base='libnfs-libnfs-5.0.2',
 | 
				
			||||||
    autoreconf=True,
 | 
					    autoreconf=True,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -446,7 +661,7 @@ jack = JackProject(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
boost = BoostProject(
 | 
					boost = BoostProject(
 | 
				
			||||||
    'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2',
 | 
					    'https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2',
 | 
				
			||||||
    '475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39',
 | 
					    '71feeed900fbccca04a3b4f2f84a7c217186f28a940ed8b7ed4725986baf99fa',
 | 
				
			||||||
    'include/boost/version.hpp',
 | 
					    'include/boost/version.hpp',
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import subprocess
 | 
					import subprocess, multiprocessing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from build.project import Project
 | 
					from build.project import Project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,7 +10,12 @@ class MakeProject(Project):
 | 
				
			|||||||
        self.install_target = install_target
 | 
					        self.install_target = install_target
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_simultaneous_jobs(self):
 | 
					    def get_simultaneous_jobs(self):
 | 
				
			||||||
        return 12
 | 
					        try:
 | 
				
			||||||
 | 
					            # use twice as many simultaneous jobs as we have CPU cores
 | 
				
			||||||
 | 
					            return multiprocessing.cpu_count() * 2
 | 
				
			||||||
 | 
					        except NotImplementedError:
 | 
				
			||||||
 | 
					            # default to 12, if multiprocessing.cpu_count() is not implemented
 | 
				
			||||||
 | 
					            return 12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_make_args(self, toolchain):
 | 
					    def get_make_args(self, toolchain):
 | 
				
			||||||
        return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
 | 
					        return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
 | 
				
			||||||
@@ -19,7 +24,7 @@ class MakeProject(Project):
 | 
				
			|||||||
        return ['--quiet', self.install_target]
 | 
					        return ['--quiet', self.install_target]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make(self, toolchain, wd, args):
 | 
					    def make(self, toolchain, wd, args):
 | 
				
			||||||
        subprocess.check_call(['/usr/bin/make'] + args,
 | 
					        subprocess.check_call(['make'] + args,
 | 
				
			||||||
                              cwd=wd, env=toolchain.env)
 | 
					                              cwd=wd, env=toolchain.env)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def build_make(self, toolchain, wd, install=True):
 | 
					    def build_make(self, toolchain, wd, install=True):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import os.path, subprocess, sys
 | 
					import os
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
import platform
 | 
					import platform
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from build.project import Project
 | 
					from build.project import Project
 | 
				
			||||||
@@ -82,8 +83,8 @@ endian = '{endian}'
 | 
				
			|||||||
def configure(toolchain, src, build, args=()):
 | 
					def configure(toolchain, src, build, args=()):
 | 
				
			||||||
    cross_file = make_cross_file(toolchain)
 | 
					    cross_file = make_cross_file(toolchain)
 | 
				
			||||||
    configure = [
 | 
					    configure = [
 | 
				
			||||||
        'meson',
 | 
					        'meson', 'setup',
 | 
				
			||||||
        src, build,
 | 
					        build, src,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '--prefix', toolchain.install_prefix,
 | 
					        '--prefix', toolchain.install_prefix,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -115,5 +116,5 @@ class MesonProject(Project):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def _build(self, toolchain):
 | 
					    def _build(self, toolchain):
 | 
				
			||||||
        build = self.configure(toolchain)
 | 
					        build = self.configure(toolchain)
 | 
				
			||||||
        subprocess.check_call(['ninja', 'install'],
 | 
					        subprocess.check_call(['ninja', '-v', 'install'],
 | 
				
			||||||
                              cwd=build, env=toolchain.env)
 | 
					                              cwd=build, env=toolchain.env)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,13 +14,14 @@ class Project:
 | 
				
			|||||||
        if base is None:
 | 
					        if base is None:
 | 
				
			||||||
            basename = os.path.basename(url)
 | 
					            basename = os.path.basename(url)
 | 
				
			||||||
            m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
 | 
					            m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
 | 
				
			||||||
            if not m: raise
 | 
					            if not m: raise RuntimeError('Could not identify tarball name: ' + basename)
 | 
				
			||||||
            self.base = m.group(1)
 | 
					            self.base = m.group(1)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.base = base
 | 
					            self.base = base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if name is None or version is None:
 | 
					        if name is None or version is None:
 | 
				
			||||||
            m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
 | 
					            m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
 | 
				
			||||||
 | 
					            if not m: raise RuntimeError('Could not identify tarball name: ' + self.base)
 | 
				
			||||||
            if name is None: name = m.group(1)
 | 
					            if name is None: name = m.group(1)
 | 
				
			||||||
            if version is None: version = m.group(2)
 | 
					            if version is None: version = m.group(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,8 +56,8 @@ class Project:
 | 
				
			|||||||
            parent_path = toolchain.src_path
 | 
					            parent_path = toolchain.src_path
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            parent_path = toolchain.build_path
 | 
					            parent_path = toolchain.build_path
 | 
				
			||||||
        path = untar(self.download(toolchain), parent_path, self.base)
 | 
					        path = untar(self.download(toolchain), parent_path, self.base,
 | 
				
			||||||
 | 
					                     lazy=out_of_tree and self.patches is None)
 | 
				
			||||||
        if self.patches is not None:
 | 
					        if self.patches is not None:
 | 
				
			||||||
            push_all(toolchain, path, self.patches)
 | 
					            push_all(toolchain, path, self.patches)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -71,8 +72,10 @@ class Project:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return path
 | 
					        return path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make_build_path(self, toolchain):
 | 
					    def make_build_path(self, toolchain, lazy=False):
 | 
				
			||||||
        path = os.path.join(toolchain.build_path, self.base)
 | 
					        path = os.path.join(toolchain.build_path, self.base)
 | 
				
			||||||
 | 
					        if lazy and os.path.isdir(path):
 | 
				
			||||||
 | 
					            return path
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            shutil.rmtree(path)
 | 
					            shutil.rmtree(path)
 | 
				
			||||||
        except FileNotFoundError:
 | 
					        except FileNotFoundError:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,16 @@
 | 
				
			|||||||
import os, shutil, subprocess
 | 
					import os, shutil, subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def untar(tarball_path, parent_path, base):
 | 
					def untar(tarball_path, parent_path, base, lazy=False):
 | 
				
			||||||
    path = os.path.join(parent_path, base)
 | 
					    path = os.path.join(parent_path, base)
 | 
				
			||||||
 | 
					    if lazy and os.path.isdir(path):
 | 
				
			||||||
 | 
					        return path
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        shutil.rmtree(path)
 | 
					        shutil.rmtree(path)
 | 
				
			||||||
    except FileNotFoundError:
 | 
					    except FileNotFoundError:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
    os.makedirs(parent_path, exist_ok=True)
 | 
					    os.makedirs(parent_path, exist_ok=True)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path])
 | 
					        subprocess.check_call(['tar', 'xfC', tarball_path, parent_path])
 | 
				
			||||||
    except FileNotFoundError:
 | 
					    except FileNotFoundError:
 | 
				
			||||||
        import tarfile
 | 
					        import tarfile
 | 
				
			||||||
        tar = tarfile.open(tarball_path)
 | 
					        tar = tarfile.open(tarball_path)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +1,32 @@
 | 
				
			|||||||
import os.path, subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from build.project import Project
 | 
					from build.makeproject import MakeProject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ZlibProject(Project):
 | 
					class ZlibProject(MakeProject):
 | 
				
			||||||
    def __init__(self, url, md5, installed,
 | 
					    def __init__(self, url, md5, installed,
 | 
				
			||||||
                 **kwargs):
 | 
					                 **kwargs):
 | 
				
			||||||
        Project.__init__(self, url, md5, installed, **kwargs)
 | 
					        MakeProject.__init__(self, url, md5, installed, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_make_args(self, toolchain):
 | 
				
			||||||
 | 
					        return MakeProject.get_make_args(self, toolchain) + [
 | 
				
			||||||
 | 
					            'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags,
 | 
				
			||||||
 | 
					            'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags,
 | 
				
			||||||
 | 
					            'AR=' + toolchain.ar,
 | 
				
			||||||
 | 
					            'ARFLAGS=' + toolchain.arflags,
 | 
				
			||||||
 | 
					            'RANLIB=' + toolchain.ranlib,
 | 
				
			||||||
 | 
					            'LDSHARED=' + toolchain.cc + ' -shared',
 | 
				
			||||||
 | 
					            'libz.a'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_make_install_args(self, toolchain):
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'RANLIB=' + toolchain.ranlib,
 | 
				
			||||||
 | 
					            self.install_target
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _build(self, toolchain):
 | 
					    def _build(self, toolchain):
 | 
				
			||||||
        src = self.unpack(toolchain, out_of_tree=False)
 | 
					        src = self.unpack(toolchain, out_of_tree=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        subprocess.check_call(['/usr/bin/make', '--quiet',
 | 
					        subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],
 | 
				
			||||||
            '-f', 'win32/Makefile.gcc',
 | 
					                              cwd=src, env=toolchain.env)
 | 
				
			||||||
            'PREFIX=' + toolchain.arch + '-',
 | 
					        self.build_make(toolchain, src)
 | 
				
			||||||
            '-j12',
 | 
					 | 
				
			||||||
            'install',
 | 
					 | 
				
			||||||
            'INCLUDE_PATH='+ os.path.join(toolchain.install_prefix, 'include'),
 | 
					 | 
				
			||||||
            'LIBRARY_PATH=' + os.path.join(toolchain.install_prefix, 'lib'),
 | 
					 | 
				
			||||||
            'BINARY_PATH=' + os.path.join(toolchain.install_prefix, 'bin'),
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            cwd=src, env=toolchain.env)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -352,12 +352,16 @@ ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
 | 
				
			|||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case OPTION_NO_DAEMON:
 | 
							case OPTION_NO_DAEMON:
 | 
				
			||||||
 | 
					#ifdef ENABLE_DAEMON
 | 
				
			||||||
			options.daemon = false;
 | 
								options.daemon = false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef __linux__
 | 
					#ifdef __linux__
 | 
				
			||||||
		case OPTION_SYSTEMD:
 | 
							case OPTION_SYSTEMD:
 | 
				
			||||||
 | 
					#ifdef ENABLE_DAEMON
 | 
				
			||||||
			options.daemon = false;
 | 
								options.daemon = false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
			options.systemd = true;
 | 
								options.systemd = true;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,11 +20,18 @@
 | 
				
			|||||||
#ifndef MPD_COMMAND_LINE_HXX
 | 
					#ifndef MPD_COMMAND_LINE_HXX
 | 
				
			||||||
#define MPD_COMMAND_LINE_HXX
 | 
					#define MPD_COMMAND_LINE_HXX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "config.h" // for ENABLE_DAEMON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ConfigData;
 | 
					struct ConfigData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct CommandLineOptions {
 | 
					struct CommandLineOptions {
 | 
				
			||||||
	bool kill = false;
 | 
						bool kill = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef ENABLE_DAEMON
 | 
				
			||||||
	bool daemon = true;
 | 
						bool daemon = true;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
						static constexpr bool daemon = false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef __linux__
 | 
					#ifdef __linux__
 | 
				
			||||||
	bool systemd = false;
 | 
						bool systemd = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
 | 
				
			|||||||
			    getenv("NOTIFY_SOCKET") != nullptr) {
 | 
								    getenv("NOTIFY_SOCKET") != nullptr) {
 | 
				
			||||||
				/* if MPD was started as a systemd
 | 
									/* if MPD was started as a systemd
 | 
				
			||||||
				   service, default to journal (which
 | 
									   service, default to journal (which
 | 
				
			||||||
				   is connected to fd=2) */
 | 
									   is connected to stdout&stderr) */
 | 
				
			||||||
				out_fd = STDOUT_FILENO;
 | 
									out_fd = STDOUT_FILENO;
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifndef HAVE_SYSLOG
 | 
					#ifdef _WIN32
 | 
				
			||||||
 | 
								/* default to stdout on Windows */
 | 
				
			||||||
 | 
								out_fd = STDOUT_FILENO;
 | 
				
			||||||
 | 
					#elif !defined(HAVE_SYSLOG)
 | 
				
			||||||
			throw std::runtime_error("config parameter 'log_file' not found");
 | 
								throw std::runtime_error("config parameter 'log_file' not found");
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef HAVE_SYSLOG
 | 
					#ifdef HAVE_SYSLOG
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										53
									
								
								src/Main.cxx
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								src/Main.cxx
									
									
									
									
									
								
							@@ -482,7 +482,10 @@ MainConfigured(const CommandLineOptions &options,
 | 
				
			|||||||
#ifndef ANDROID
 | 
					#ifndef ANDROID
 | 
				
			||||||
	setup_log_output();
 | 
						setup_log_output();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const ScopeSignalHandlersInit signal_handlers_init(instance);
 | 
						const ScopeSignalHandlersInit signal_handlers_init{
 | 
				
			||||||
 | 
							instance,
 | 
				
			||||||
 | 
							options.daemon,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	instance.io_thread.Start();
 | 
						instance.io_thread.Start();
 | 
				
			||||||
@@ -590,19 +593,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 +644,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 +658,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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return std::strchr(name_utf8, '/') == nullptr &&
 | 
						return std::strchr(name_utf8, '/') == nullptr &&
 | 
				
			||||||
 | 
					#ifdef _WIN32
 | 
				
			||||||
 | 
							std::strchr(name_utf8, '\\') == nullptr &&
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
		std::strchr(name_utf8, '\n') == nullptr &&
 | 
							std::strchr(name_utf8, '\n') == nullptr &&
 | 
				
			||||||
		std::strchr(name_utf8, '\r') == nullptr;
 | 
							std::strchr(name_utf8, '\r') == nullptr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,11 @@
 | 
				
			|||||||
#include <boost/intrusive/list.hpp>
 | 
					#include <boost/intrusive/list.hpp>
 | 
				
			||||||
#include <boost/intrusive/unordered_set.hpp>
 | 
					#include <boost/intrusive/unordered_set.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <array>
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RemoteTagCacheHandler;
 | 
					class RemoteTagCacheHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,7 @@
 | 
				
			|||||||
#include "TagPrint.hxx"
 | 
					#include "TagPrint.hxx"
 | 
				
			||||||
#include "client/Response.hxx"
 | 
					#include "client/Response.hxx"
 | 
				
			||||||
#include "fs/Traits.hxx"
 | 
					#include "fs/Traits.hxx"
 | 
				
			||||||
 | 
					#include "lib/fmt/AudioFormatFormatter.hxx"
 | 
				
			||||||
#include "time/ChronoUtil.hxx"
 | 
					#include "time/ChronoUtil.hxx"
 | 
				
			||||||
#include "util/StringBuffer.hxx"
 | 
					#include "util/StringBuffer.hxx"
 | 
				
			||||||
#include "util/UriUtil.hxx"
 | 
					#include "util/UriUtil.hxx"
 | 
				
			||||||
@@ -93,7 +94,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
 | 
				
			|||||||
		time_print(r, "Last-Modified", song.mtime);
 | 
							time_print(r, "Last-Modified", song.mtime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (song.audio_format.IsDefined())
 | 
						if (song.audio_format.IsDefined())
 | 
				
			||||||
		r.Fmt(FMT_STRING("Format: {}\n"), ToString(song.audio_format));
 | 
							r.Fmt(FMT_STRING("Format: {}\n"), song.audio_format);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tag_print_values(r, song.tag);
 | 
						tag_print_values(r, song.tag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -116,7 +117,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
 | 
				
			|||||||
		time_print(r, "Last-Modified", song.GetLastModified());
 | 
							time_print(r, "Last-Modified", song.GetLastModified());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (const auto &f = song.GetAudioFormat(); f.IsDefined())
 | 
						if (const auto &f = song.GetAudioFormat(); f.IsDefined())
 | 
				
			||||||
		r.Fmt(FMT_STRING("Format: {}\n"), ToString(f));
 | 
							r.Fmt(FMT_STRING("Format: {}\n"), f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tag_print_values(r, song.GetTag());
 | 
						tag_print_values(r, song.GetTag());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,6 +63,9 @@ song_save(BufferedOutputStream &os, const Song &song)
 | 
				
			|||||||
	if (song.audio_format.IsDefined())
 | 
						if (song.audio_format.IsDefined())
 | 
				
			||||||
		os.Format("Format: %s\n", ToString(song.audio_format).c_str());
 | 
							os.Format("Format: %s\n", ToString(song.audio_format).c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (song.in_playlist)
 | 
				
			||||||
 | 
							os.Write("InPlaylist: yes\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!IsNegative(song.mtime))
 | 
						if (!IsNegative(song.mtime))
 | 
				
			||||||
		os.Format(SONG_MTIME ": %li\n",
 | 
							os.Format(SONG_MTIME ": %li\n",
 | 
				
			||||||
			  (long)std::chrono::system_clock::to_time_t(song.mtime));
 | 
								  (long)std::chrono::system_clock::to_time_t(song.mtime));
 | 
				
			||||||
@@ -86,7 +89,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
DetachedSong
 | 
					DetachedSong
 | 
				
			||||||
song_load(LineReader &file, const char *uri,
 | 
					song_load(LineReader &file, const char *uri,
 | 
				
			||||||
	  std::string *target_r)
 | 
						  std::string *target_r, bool *in_playlist_r)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	DetachedSong song(uri);
 | 
						DetachedSong song(uri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -132,6 +135,9 @@ song_load(LineReader &file, const char *uri,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			song.SetStartTime(SongTime::FromMS(start_ms));
 | 
								song.SetStartTime(SongTime::FromMS(start_ms));
 | 
				
			||||||
			song.SetEndTime(SongTime::FromMS(end_ms));
 | 
								song.SetEndTime(SongTime::FromMS(end_ms));
 | 
				
			||||||
 | 
							} else if (StringIsEqual(line, "InPlaylist")) {
 | 
				
			||||||
 | 
								if (in_playlist_r != nullptr)
 | 
				
			||||||
 | 
									*in_playlist_r = StringIsEqual(value, "yes");
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			throw FormatRuntimeError("unknown line in db: %s", line);
 | 
								throw FormatRuntimeError("unknown line in db: %s", line);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,6 +44,6 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
DetachedSong
 | 
					DetachedSong
 | 
				
			||||||
song_load(LineReader &file, const char *uri,
 | 
					song_load(LineReader &file, const char *uri,
 | 
				
			||||||
	  std::string *target_r=nullptr);
 | 
						  std::string *target_r=nullptr, bool *in_playlist_r=nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
#ifdef ANDROID
 | 
					#ifdef ANDROID
 | 
				
			||||||
	if (path.IsNull()) {
 | 
						if (path.IsNull()) {
 | 
				
			||||||
		const auto cache_dir = GetUserCacheDir();
 | 
							const auto cache_dir = GetAppCacheDir();
 | 
				
			||||||
		if (cache_dir.IsNull())
 | 
							if (cache_dir.IsNull())
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,8 +35,9 @@ tag_print_types(Response &r) noexcept
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
tag_print(Response &r, TagType type, StringView value) noexcept
 | 
					tag_print(Response &r, TagType type, StringView _value) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						const std::string_view value{_value};
 | 
				
			||||||
	r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
 | 
						r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,5 +36,5 @@ time_print(Response &r, const char *name,
 | 
				
			|||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r.Fmt(FMT_STRING("{}: {}\n"), name, s);
 | 
						r.Fmt(FMT_STRING("{}: {}\n"), name, s.c_str());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,19 +26,30 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "AudioManager.hxx"
 | 
					#include "AudioManager.hxx"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AllocatedPath
 | 
					static jmethodID getExternalFilesDir_method,
 | 
				
			||||||
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
 | 
					  getCacheDir_method,
 | 
				
			||||||
 | 
					  getSystemService_method;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					Context::Initialise(JNIEnv *env) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	assert(_type != nullptr);
 | 
						Java::Class cls{env, "android/content/Context"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Java::Class cls{env, env->GetObjectClass(Get())};
 | 
						getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
 | 
				
			||||||
	jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
 | 
											      "(Ljava/lang/String;)Ljava/io/File;");
 | 
				
			||||||
					    "(Ljava/lang/String;)Ljava/io/File;");
 | 
						getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
 | 
				
			||||||
	assert(method);
 | 
										      "()Ljava/io/File;");
 | 
				
			||||||
 | 
						getSystemService_method = env->GetMethodID(cls, "getSystemService",
 | 
				
			||||||
 | 
											   "(Ljava/lang/String;)Ljava/lang/Object;");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Java::String type{env, _type};
 | 
					AllocatedPath
 | 
				
			||||||
 | 
					Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						assert(type != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	jobject file = env->CallObjectMethod(Get(), method, type.Get());
 | 
						jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method,
 | 
				
			||||||
 | 
										     Java::String::Optional(env, 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 jmethodID getExternalStorageDirectory_method;
 | 
					static Java::TrivialClass cls;
 | 
				
			||||||
	static jmethodID getExternalStoragePublicDirectory_method;
 | 
					static jmethodID getExternalStorageDirectory_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
 | 
				
			||||||
	 * Determine the mount point of the external SD card.
 | 
					Initialise(JNIEnv *env) noexcept;
 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	[[gnu::pure]]
 | 
					 | 
				
			||||||
	AllocatedPath getExternalStorageDirectory() noexcept;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[gnu::pure]]
 | 
					void
 | 
				
			||||||
	AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
 | 
					Deinitialise(JNIEnv *env) noexcept;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					/**
 | 
				
			||||||
 | 
					 * Determine the mount point of the external SD card.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
 | 
					AllocatedPath
 | 
				
			||||||
 | 
					getExternalStorageDirectory(JNIEnv *env) noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
 | 
					AllocatedPath
 | 
				
			||||||
 | 
					getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Environment
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream {
 | 
				
			|||||||
			assert(fill <= data.size());
 | 
								assert(fill <= data.size());
 | 
				
			||||||
			assert(position <= fill);
 | 
								assert(position <= fill);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return {&data[position], &data[fill]};
 | 
								return {data.data() + position, data.data() + fill};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		void Consume(size_t nbytes) noexcept {
 | 
							void Consume(size_t nbytes) noexcept {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,10 @@ if libzzip_dep.found()
 | 
				
			|||||||
  found_archive_plugin = true
 | 
					  found_archive_plugin = true
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if not found_archive_plugin
 | 
				
			||||||
 | 
					  subdir_done()
 | 
				
			||||||
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive_plugins = static_library(
 | 
					archive_plugins = static_library(
 | 
				
			||||||
  'archive_plugins',
 | 
					  'archive_plugins',
 | 
				
			||||||
  archive_plugins_sources,
 | 
					  archive_plugins_sources,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,6 +41,8 @@
 | 
				
			|||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <limits.h> // for UINT_MAX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CommandResult
 | 
					CommandResult
 | 
				
			||||||
handle_listfiles_db(Client &client, Response &r, const char *uri)
 | 
					handle_listfiles_db(Client &client, Response &r, const char *uri)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -100,10 +100,6 @@ handle_listfiles_local(Response &r, Path path_fs)
 | 
				
			|||||||
	return CommandResult::OK;
 | 
						return CommandResult::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
 | 
					 | 
				
			||||||
#pragma GCC diagnostic pop
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
gcc_pure
 | 
					gcc_pure
 | 
				
			||||||
static bool
 | 
					static bool
 | 
				
			||||||
IsValidName(const StringView s) noexcept
 | 
					IsValidName(const StringView s) noexcept
 | 
				
			||||||
@@ -130,7 +126,8 @@ public:
 | 
				
			|||||||
	explicit PrintCommentHandler(Response &_response) noexcept
 | 
						explicit PrintCommentHandler(Response &_response) noexcept
 | 
				
			||||||
		:NullTagHandler(WANT_PAIR), response(_response) {}
 | 
							:NullTagHandler(WANT_PAIR), response(_response) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void OnPair(StringView key, StringView value) noexcept override {
 | 
						void OnPair(StringView _key, StringView _value) noexcept override {
 | 
				
			||||||
 | 
							const std::string_view key{_key}, value{_value};
 | 
				
			||||||
		if (IsValidName(key) && IsValidValue(value))
 | 
							if (IsValidName(key) && IsValidValue(value))
 | 
				
			||||||
			response.Fmt(FMT_STRING("{}: {}\n"), key, value);
 | 
								response.Fmt(FMT_STRING("{}: {}\n"), key, value);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -337,7 +336,9 @@ handle_setvol(Client &client, Request args, Response &)
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	unsigned level = args.ParseUnsigned(0, 100);
 | 
						unsigned level = args.ParseUnsigned(0, 100);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	volume_level_change(client.GetPartition().outputs, level);
 | 
						auto &partition = client.GetPartition();
 | 
				
			||||||
 | 
						partition.mixer_memento.SetVolume(partition.outputs, level);
 | 
				
			||||||
 | 
						partition.EmitIdle(IDLE_MIXER);
 | 
				
			||||||
	return CommandResult::OK;
 | 
						return CommandResult::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -346,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;
 | 
				
			||||||
@@ -360,8 +363,10 @@ 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);
 | 
				
			||||||
 | 
							partition.EmitIdle(IDLE_MIXER);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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,10 +25,10 @@
 | 
				
			|||||||
#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"
 | 
				
			||||||
 | 
					#include "lib/fmt/AudioFormatFormatter.hxx"
 | 
				
			||||||
#include "util/StringBuffer.hxx"
 | 
					#include "util/StringBuffer.hxx"
 | 
				
			||||||
#include "util/ScopeExit.hxx"
 | 
					#include "util/ScopeExit.hxx"
 | 
				
			||||||
#include "util/Exception.hxx"
 | 
					#include "util/Exception.hxx"
 | 
				
			||||||
@@ -131,7 +131,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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -186,7 +186,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		if (player_status.audio_format.IsDefined())
 | 
							if (player_status.audio_format.IsDefined())
 | 
				
			||||||
			r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"),
 | 
								r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"),
 | 
				
			||||||
			      ToString(player_status.audio_format));
 | 
								      player_status.audio_format);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ENABLE_DATABASE
 | 
					#ifdef ENABLE_DATABASE
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,10 +83,6 @@ handle_listfiles_storage(Response &r, StorageDirectoryReader &reader)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
 | 
					 | 
				
			||||||
#pragma GCC diagnostic pop
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CommandResult
 | 
					CommandResult
 | 
				
			||||||
handle_listfiles_storage(Response &r, Storage &storage, const char *uri)
 | 
					handle_listfiles_storage(Response &r, Storage &storage, const char *uri)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,7 @@
 | 
				
			|||||||
#include "config/Param.hxx"
 | 
					#include "config/Param.hxx"
 | 
				
			||||||
#include "config/Block.hxx"
 | 
					#include "config/Block.hxx"
 | 
				
			||||||
#include "fs/AllocatedPath.hxx"
 | 
					#include "fs/AllocatedPath.hxx"
 | 
				
			||||||
 | 
					#include "fs/FileSystem.hxx"
 | 
				
			||||||
#include "fs/StandardDirectory.hxx"
 | 
					#include "fs/StandardDirectory.hxx"
 | 
				
			||||||
#include "util/RuntimeError.hxx"
 | 
					#include "util/RuntimeError.hxx"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
 | 
				
			|||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		/* if there is no override, use the cache directory */
 | 
							/* if there is no override, use the cache directory */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const AllocatedPath cache_dir = GetUserCacheDir();
 | 
							const AllocatedPath cache_dir = GetAppCacheDir();
 | 
				
			||||||
		if (cache_dir.IsNull())
 | 
							if (cache_dir.IsNull())
 | 
				
			||||||
			return nullptr;
 | 
								return nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("mpd.db"));
 | 
							const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("db"));
 | 
				
			||||||
		auto db_file_utf8 = db_file.ToUTF8();
 | 
							auto db_file_utf8 = db_file.ToUTF8();
 | 
				
			||||||
		if (db_file_utf8.empty())
 | 
							if (db_file_utf8.empty())
 | 
				
			||||||
			return nullptr;
 | 
								return nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ConfigBlock block;
 | 
							ConfigBlock block;
 | 
				
			||||||
		block.AddBlockParam("path", std::move(db_file_utf8), -1);
 | 
							block.AddBlockParam("path", std::move(db_file_utf8), -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const auto mounts_dir = cache_dir
 | 
				
			||||||
 | 
									/ Path::FromFS(PATH_LITERAL("mounts"));
 | 
				
			||||||
 | 
								CreateDirectoryNoThrow(mounts_dir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (auto mounts_dir_utf8 = mounts_dir.ToUTF8();
 | 
				
			||||||
 | 
								    !mounts_dir_utf8.empty())
 | 
				
			||||||
 | 
									block.AddBlockParam("cache_directory",
 | 
				
			||||||
 | 
											    std::move(mounts_dir_utf8),
 | 
				
			||||||
 | 
											    -1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return DatabaseGlobalInit(main_event_loop, io_event_loop,
 | 
							return DatabaseGlobalInit(main_event_loop, io_event_loop,
 | 
				
			||||||
					  listener, block);
 | 
										  listener, block);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,6 +126,18 @@ Directory::LookupTargetSong(std::string_view _target) noexcept
 | 
				
			|||||||
	return lr.directory->FindSong(lr.rest);
 | 
						return lr.directory->FindSong(lr.rest);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					Directory::ClearInPlaylist() noexcept
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						assert(holding_db_lock());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto &child : children)
 | 
				
			||||||
 | 
							child.ClearInPlaylist();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto &song : songs)
 | 
				
			||||||
 | 
							song.in_playlist = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
Directory::PruneEmpty() noexcept
 | 
					Directory::PruneEmpty() noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -287,6 +287,14 @@ public:
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	SongPtr RemoveSong(Song *song) noexcept;
 | 
						SongPtr RemoveSong(Song *song) noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Recursively walk through the whole tree and set all
 | 
				
			||||||
 | 
						 * `Song::in_playlist` fields to `false`.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * Caller must lock the #db_mutex.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						void ClearInPlaylist() noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Caller must lock the #db_mutex.
 | 
						 * Caller must lock the #db_mutex.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -168,12 +168,14 @@ directory_load(LineReader &file, Directory &directory)
 | 
				
			|||||||
				throw FormatRuntimeError("Duplicate song '%s'", name);
 | 
									throw FormatRuntimeError("Duplicate song '%s'", name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			std::string target;
 | 
								std::string target;
 | 
				
			||||||
 | 
								bool in_playlist = false;
 | 
				
			||||||
			auto detached_song = song_load(file, name,
 | 
								auto detached_song = song_load(file, name,
 | 
				
			||||||
						       &target);
 | 
											       &target, &in_playlist);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			auto song = std::make_unique<Song>(std::move(detached_song),
 | 
								auto song = std::make_unique<Song>(std::move(detached_song),
 | 
				
			||||||
							   directory);
 | 
												   directory);
 | 
				
			||||||
			song->target = std::move(target);
 | 
								song->target = std::move(target);
 | 
				
			||||||
 | 
								song->in_playlist = in_playlist;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			directory.AddSong(std::move(song));
 | 
								directory.AddSong(std::move(song));
 | 
				
			||||||
		} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
 | 
							} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,15 @@ LockFindSong(Directory &directory, std::string_view name) noexcept
 | 
				
			|||||||
	return directory.FindSong(name);
 | 
						return directory.FindSong(name);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					IsAcceptableFilename(std::string_view name) noexcept
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return !name.empty() &&
 | 
				
			||||||
 | 
							/* newlines cannot be represented in MPD's protocol */
 | 
				
			||||||
 | 
							name.find('\n') == name.npos;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
 | 
					UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
 | 
				
			||||||
			      const char *name) noexcept
 | 
								      const char *name) noexcept
 | 
				
			||||||
@@ -58,6 +67,9 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
 | 
				
			|||||||
	const char *tmp = std::strchr(name, '/');
 | 
						const char *tmp = std::strchr(name, '/');
 | 
				
			||||||
	if (tmp) {
 | 
						if (tmp) {
 | 
				
			||||||
		const std::string_view child_name(name, tmp - name);
 | 
							const std::string_view child_name(name, tmp - name);
 | 
				
			||||||
 | 
							if (!IsAcceptableFilename(child_name))
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//add dir is not there already
 | 
							//add dir is not there already
 | 
				
			||||||
		Directory *subdir = LockMakeChild(directory, child_name);
 | 
							Directory *subdir = LockMakeChild(directory, child_name);
 | 
				
			||||||
		subdir->device = DEVICE_INARCHIVE;
 | 
							subdir->device = DEVICE_INARCHIVE;
 | 
				
			||||||
@@ -65,11 +77,8 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
 | 
				
			|||||||
		//create directories first
 | 
							//create directories first
 | 
				
			||||||
		UpdateArchiveTree(archive, *subdir, tmp + 1);
 | 
							UpdateArchiveTree(archive, *subdir, tmp + 1);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		if (StringIsEmpty(name)) {
 | 
							if (!IsAcceptableFilename(name))
 | 
				
			||||||
			LogWarning(update_domain,
 | 
					 | 
				
			||||||
				   "archive returned directory only");
 | 
					 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//add file
 | 
							//add file
 | 
				
			||||||
		Song *song = LockFindSong(directory, name);
 | 
							Song *song = LockFindSong(directory, name);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -531,6 +531,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		const ScopeDatabaseLock protect;
 | 
							const ScopeDatabaseLock protect;
 | 
				
			||||||
 | 
							root.ClearInPlaylist();
 | 
				
			||||||
		PurgeDanglingFromPlaylists(root);
 | 
							PurgeDanglingFromPlaylists(root);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,11 +114,11 @@ constexpr const struct DecoderPlugin *decoder_plugins[] = {
 | 
				
			|||||||
#ifdef ENABLE_ADPLUG
 | 
					#ifdef ENABLE_ADPLUG
 | 
				
			||||||
	&adplug_decoder_plugin,
 | 
						&adplug_decoder_plugin,
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef ENABLE_FFMPEG
 | 
					 | 
				
			||||||
	&ffmpeg_decoder_plugin,
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#ifdef ENABLE_GME
 | 
					#ifdef ENABLE_GME
 | 
				
			||||||
	&gme_decoder_plugin,
 | 
						&gme_decoder_plugin,
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef ENABLE_FFMPEG
 | 
				
			||||||
 | 
						&ffmpeg_decoder_plugin,
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
	&pcm_decoder_plugin,
 | 
						&pcm_decoder_plugin,
 | 
				
			||||||
	nullptr
 | 
						nullptr
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,7 @@
 | 
				
			|||||||
#include "lib/ffmpeg/Format.hxx"
 | 
					#include "lib/ffmpeg/Format.hxx"
 | 
				
			||||||
#include "lib/ffmpeg/Codec.hxx"
 | 
					#include "lib/ffmpeg/Codec.hxx"
 | 
				
			||||||
#include "lib/ffmpeg/SampleFormat.hxx"
 | 
					#include "lib/ffmpeg/SampleFormat.hxx"
 | 
				
			||||||
 | 
					#include "lib/ffmpeg/LibFmt.hxx"
 | 
				
			||||||
#include "../DecoderAPI.hxx"
 | 
					#include "../DecoderAPI.hxx"
 | 
				
			||||||
#include "FfmpegMetaData.hxx"
 | 
					#include "FfmpegMetaData.hxx"
 | 
				
			||||||
#include "FfmpegIo.hxx"
 | 
					#include "FfmpegIo.hxx"
 | 
				
			||||||
@@ -523,9 +524,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
 | 
				
			||||||
@@ -635,10 +642,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) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,20 +56,17 @@ struct GmeContainerPath {
 | 
				
			|||||||
	unsigned track;
 | 
						unsigned track;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if GME_VERSION >= 0x000600
 | 
					 | 
				
			||||||
static int gme_accuracy;
 | 
					static int gme_accuracy;
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
static unsigned gme_default_fade;
 | 
					static unsigned gme_default_fade;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool
 | 
					static bool
 | 
				
			||||||
gme_plugin_init([[maybe_unused]] const ConfigBlock &block)
 | 
					gme_plugin_init([[maybe_unused]] const ConfigBlock &block)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
#if GME_VERSION >= 0x000600
 | 
					 | 
				
			||||||
	auto accuracy = block.GetBlockParam("accuracy");
 | 
						auto accuracy = block.GetBlockParam("accuracy");
 | 
				
			||||||
	gme_accuracy = accuracy != nullptr
 | 
						gme_accuracy = accuracy != nullptr
 | 
				
			||||||
		? (int)accuracy->GetBoolValue()
 | 
							? (int)accuracy->GetBoolValue()
 | 
				
			||||||
		: -1;
 | 
							: -1;
 | 
				
			||||||
#endif
 | 
					
 | 
				
			||||||
	auto fade = block.GetBlockParam("default_fade");
 | 
						auto fade = block.GetBlockParam("default_fade");
 | 
				
			||||||
	gme_default_fade = fade != nullptr
 | 
						gme_default_fade = fade != nullptr
 | 
				
			||||||
		? fade->GetUnsignedValue() * 1000
 | 
							? fade->GetUnsignedValue() * 1000
 | 
				
			||||||
@@ -163,10 +160,8 @@ gme_file_decode(DecoderClient &client, Path path_fs)
 | 
				
			|||||||
	FmtDebug(gme_domain, "emulator type '{}'",
 | 
						FmtDebug(gme_domain, "emulator type '{}'",
 | 
				
			||||||
		 gme_type_system(gme_type(emu)));
 | 
							 gme_type_system(gme_type(emu)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if GME_VERSION >= 0x000600
 | 
					 | 
				
			||||||
	if (gme_accuracy >= 0)
 | 
						if (gme_accuracy >= 0)
 | 
				
			||||||
		gme_enable_accuracy(emu, gme_accuracy);
 | 
							gme_enable_accuracy(emu, gme_accuracy);
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gme_info_t *ti;
 | 
						gme_info_t *ti;
 | 
				
			||||||
	const char *gme_err = gme_track_info(emu, &ti, container.track);
 | 
						const char *gme_err = gme_track_info(emu, &ti, container.track);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -798,6 +798,8 @@ MadDecoder::UpdateTimerNextFrame() noexcept
 | 
				
			|||||||
DecoderCommand
 | 
					DecoderCommand
 | 
				
			||||||
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
 | 
					MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						assert(i <= pcm_length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	size_t num_samples = pcm_length - i;
 | 
						size_t num_samples = pcm_length - i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mad_fixed_to_24_buffer(output_buffer, synth.pcm,
 | 
						mad_fixed_to_24_buffer(output_buffer, synth.pcm,
 | 
				
			||||||
@@ -843,7 +845,7 @@ MadDecoder::SynthAndSubmit() noexcept
 | 
				
			|||||||
	size_t pcm_length = synth.pcm.length;
 | 
						size_t pcm_length = synth.pcm.length;
 | 
				
			||||||
	if (drop_end_samples &&
 | 
						if (drop_end_samples &&
 | 
				
			||||||
	    current_frame == max_frames - drop_end_frames - 1) {
 | 
						    current_frame == max_frames - drop_end_frames - 1) {
 | 
				
			||||||
		if (drop_end_samples >= pcm_length)
 | 
							if (i + drop_end_samples >= pcm_length)
 | 
				
			||||||
			return DecoderCommand::STOP;
 | 
								return DecoderCommand::STOP;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pcm_length -= drop_end_samples;
 | 
							pcm_length -= drop_end_samples;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -81,7 +81,7 @@ if libfaad_dep.found()
 | 
				
			|||||||
  decoder_plugins_sources += 'FaadDecoderPlugin.cxx'
 | 
					  decoder_plugins_sources += 'FaadDecoderPlugin.cxx'
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
libgme_dep = c_compiler.find_library('gme', required: get_option('gme'))
 | 
					libgme_dep = dependency('libgme', version: '>= 0.6', required: get_option('gme'))
 | 
				
			||||||
decoder_features.set('ENABLE_GME', libgme_dep.found())
 | 
					decoder_features.set('ENABLE_GME', libgme_dep.found())
 | 
				
			||||||
if libgme_dep.found()
 | 
					if libgme_dep.found()
 | 
				
			||||||
  decoder_plugins_sources += 'GmeDecoderPlugin.cxx'
 | 
					  decoder_plugins_sources += 'GmeDecoderPlugin.cxx'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	FLAC__StreamEncoder *const fse;
 | 
						FLAC__StreamEncoder *const fse;
 | 
				
			||||||
	const unsigned compression;
 | 
						const unsigned compression;
 | 
				
			||||||
 | 
						const bool oggflac;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	PcmBuffer expand_buffer;
 | 
						PcmBuffer expand_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
 | 
					flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
 | 
				
			||||||
		   const AudioFormat &audio_format)
 | 
							   const AudioFormat &audio_format)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	unsigned bits_per_sample;
 | 
						unsigned bits_per_sample;
 | 
				
			||||||
@@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
 | 
				
			|||||||
		throw FormatRuntimeError("error setting flac sample rate to %d",
 | 
							throw FormatRuntimeError("error setting flac sample rate to %d",
 | 
				
			||||||
					 audio_format.sample_rate);
 | 
										 audio_format.sample_rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!FLAC__stream_encoder_set_ogg_serial_number(fse,
 | 
						if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
 | 
				
			||||||
						  GenerateSerial()))
 | 
											  GenerateSerial()))
 | 
				
			||||||
		throw FormatRuntimeError("error setting ogg serial number");
 | 
							throw FormatRuntimeError("error setting ogg serial number");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
 | 
				
			|||||||
	:Encoder(_oggchaining),
 | 
						:Encoder(_oggchaining),
 | 
				
			||||||
	 audio_format(_audio_format), fse(_fse),
 | 
						 audio_format(_audio_format), fse(_fse),
 | 
				
			||||||
	 compression(_compression),
 | 
						 compression(_compression),
 | 
				
			||||||
 | 
						 oggflac(_oggflac),
 | 
				
			||||||
	 output_buffer(8192)
 | 
						 output_buffer(8192)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	/* this immediately outputs data through callback */
 | 
						/* this immediately outputs data through callback */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto init_status = _oggflac ?
 | 
						auto init_status = oggflac ?
 | 
				
			||||||
		FLAC__stream_encoder_init_ogg_stream(fse,
 | 
							FLAC__stream_encoder_init_ogg_stream(fse,
 | 
				
			||||||
						     nullptr, WriteCallback,
 | 
											     nullptr, WriteCallback,
 | 
				
			||||||
						     nullptr, nullptr, nullptr,
 | 
											     nullptr, nullptr, nullptr,
 | 
				
			||||||
@@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
 | 
				
			|||||||
		throw std::runtime_error("FLAC__stream_encoder_new() failed");
 | 
							throw std::runtime_error("FLAC__stream_encoder_new() failed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		flac_encoder_setup(fse, compression, audio_format);
 | 
							flac_encoder_setup(fse, compression, oggflac, audio_format);
 | 
				
			||||||
	} catch (...) {
 | 
						} catch (...) {
 | 
				
			||||||
		FLAC__stream_encoder_delete(fse);
 | 
							FLAC__stream_encoder_delete(fse);
 | 
				
			||||||
		throw;
 | 
							throw;
 | 
				
			||||||
@@ -222,7 +224,7 @@ void
 | 
				
			|||||||
FlacEncoder::SendTag(const Tag &tag)
 | 
					FlacEncoder::SendTag(const Tag &tag)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	/* re-initialize encoder since flac_encoder_finish resets everything */
 | 
						/* re-initialize encoder since flac_encoder_finish resets everything */
 | 
				
			||||||
	flac_encoder_setup(fse, compression, audio_format);
 | 
						flac_encoder_setup(fse, compression, oggflac, audio_format);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
 | 
						FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
 | 
				
			||||||
	FLAC__StreamMetadata_VorbisComment_Entry entry;
 | 
						FLAC__StreamMetadata_VorbisComment_Entry entry;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -272,9 +272,8 @@ EventLoop::Run() noexcept
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert(IsInside());
 | 
						assert(IsInside());
 | 
				
			||||||
	assert(!quit);
 | 
					 | 
				
			||||||
#ifdef HAVE_THREADED_EVENT_LOOP
 | 
					#ifdef HAVE_THREADED_EVENT_LOOP
 | 
				
			||||||
	assert(alive);
 | 
						assert(alive || quit);
 | 
				
			||||||
	assert(busy);
 | 
						assert(busy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	wake_event.Schedule(SocketEvent::READ);
 | 
						wake_event.Schedule(SocketEvent::READ);
 | 
				
			||||||
@@ -299,7 +298,7 @@ EventLoop::Run() noexcept
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	steady_clock_cache.flush();
 | 
						steady_clock_cache.flush();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	do {
 | 
						while (!quit) {
 | 
				
			||||||
		again = false;
 | 
							again = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* invoke timers */
 | 
							/* invoke timers */
 | 
				
			||||||
@@ -361,7 +360,7 @@ EventLoop::Run() noexcept
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			socket_event.Dispatch();
 | 
								socket_event.Dispatch();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} while (!quit);
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef HAVE_THREADED_EVENT_LOOP
 | 
					#ifdef HAVE_THREADED_EVENT_LOOP
 | 
				
			||||||
#ifndef NDEBUG
 | 
					#ifndef NDEBUG
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@
 | 
				
			|||||||
#include "Charset.hxx"
 | 
					#include "Charset.hxx"
 | 
				
			||||||
#include "Features.hxx"
 | 
					#include "Features.hxx"
 | 
				
			||||||
#include "Domain.hxx"
 | 
					#include "Domain.hxx"
 | 
				
			||||||
#include "Log.hxx"
 | 
					 | 
				
			||||||
#include "lib/icu/Converter.hxx"
 | 
					#include "lib/icu/Converter.hxx"
 | 
				
			||||||
#include "util/AllocatedString.hxx"
 | 
					#include "util/AllocatedString.hxx"
 | 
				
			||||||
#include "config.h"
 | 
					#include "config.h"
 | 
				
			||||||
@@ -45,11 +44,9 @@ SetFSCharset(const char *charset)
 | 
				
			|||||||
	assert(charset != nullptr);
 | 
						assert(charset != nullptr);
 | 
				
			||||||
	assert(fs_converter == nullptr);
 | 
						assert(fs_converter == nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fs_charset = charset;
 | 
				
			||||||
	fs_converter = IcuConverter::Create(charset);
 | 
						fs_converter = IcuConverter::Create(charset);
 | 
				
			||||||
	assert(fs_converter != nullptr);
 | 
						assert(fs_converter != nullptr);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	FmtDebug(path_domain,
 | 
					 | 
				
			||||||
		 "SetFSCharset: fs charset is {}", fs_charset);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline bool
 | 
				
			||||||
 | 
					CreateDirectoryNoThrow(Path path) noexcept
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					#ifdef _WIN32
 | 
				
			||||||
 | 
						return CreateDirectory(path.c_str(), nullptr);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
						return mkdir(path.c_str(), 0777);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Truncate a file that exists already.  Throws std::system_error on
 | 
					 * Truncate a file that exists already.  Throws std::system_error on
 | 
				
			||||||
 * error.
 | 
					 * error.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,6 @@
 | 
				
			|||||||
#include <shlobj.h>
 | 
					#include <shlobj.h>
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
#include <stdlib.h>
 | 
					#include <stdlib.h>
 | 
				
			||||||
#include <unistd.h>
 | 
					 | 
				
			||||||
#include <pwd.h>
 | 
					#include <pwd.h>
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,6 +52,12 @@
 | 
				
			|||||||
#include "Main.hxx"
 | 
					#include "Main.hxx"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_XDG
 | 
				
			||||||
 | 
					#include "Version.h" // for PACKAGE_NAME
 | 
				
			||||||
 | 
					#define APP_FILENAME PATH_LITERAL(PACKAGE_NAME)
 | 
				
			||||||
 | 
					static constexpr Path app_filename = Path::FromFS(APP_FILENAME);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if !defined(_WIN32) && !defined(ANDROID)
 | 
					#if !defined(_WIN32) && !defined(ANDROID)
 | 
				
			||||||
class PasswdEntry
 | 
					class PasswdEntry
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -74,15 +79,6 @@ public:
 | 
				
			|||||||
		return result != nullptr;
 | 
							return result != nullptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool ReadByUid(uid_t uid) {
 | 
					 | 
				
			||||||
#ifdef HAVE_GETPWUID_R
 | 
					 | 
				
			||||||
		getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
		result = getpwuid(uid);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
		return result != nullptr;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const passwd *operator->() {
 | 
						const passwd *operator->() {
 | 
				
			||||||
		assert(result != nullptr);
 | 
							assert(result != nullptr);
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
@@ -254,7 +250,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
 | 
				
			||||||
@@ -283,6 +280,24 @@ GetUserCacheDir() noexcept
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AllocatedPath
 | 
				
			||||||
 | 
					GetAppCacheDir() noexcept
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					#ifdef USE_XDG
 | 
				
			||||||
 | 
						if (const auto user_dir = GetUserCacheDir(); !user_dir.IsNull()) {
 | 
				
			||||||
 | 
							auto dir = user_dir / app_filename;
 | 
				
			||||||
 | 
							CreateDirectoryNoThrow(dir);
 | 
				
			||||||
 | 
							return dir;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nullptr;
 | 
				
			||||||
 | 
					#elif defined(ANDROID)
 | 
				
			||||||
 | 
						return context->GetCacheDir(Java::GetEnv());
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
						return nullptr;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AllocatedPath
 | 
					AllocatedPath
 | 
				
			||||||
GetUserRuntimeDir() noexcept
 | 
					GetUserRuntimeDir() noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -296,7 +311,7 @@ GetUserRuntimeDir() noexcept
 | 
				
			|||||||
AllocatedPath
 | 
					AllocatedPath
 | 
				
			||||||
GetAppRuntimeDir() noexcept
 | 
					GetAppRuntimeDir() noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
#ifdef __linux__
 | 
					#if defined(__linux__) && !defined(ANDROID)
 | 
				
			||||||
	/* systemd specific; see systemd.exec(5) */
 | 
						/* systemd specific; see systemd.exec(5) */
 | 
				
			||||||
	if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
 | 
						if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
 | 
				
			||||||
		if (auto dir = StringView{runtime_directory}.Split(':').first;
 | 
							if (auto dir = StringView{runtime_directory}.Split(':').first;
 | 
				
			||||||
@@ -306,8 +321,8 @@ GetAppRuntimeDir() noexcept
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#ifdef USE_XDG
 | 
					#ifdef USE_XDG
 | 
				
			||||||
	if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
 | 
						if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
 | 
				
			||||||
		auto dir = user_dir / Path::FromFS("mpd");
 | 
							auto dir = user_dir / app_filename;
 | 
				
			||||||
		mkdir(dir.c_str(), 0700);
 | 
							CreateDirectoryNoThrow(dir);
 | 
				
			||||||
		return dir;
 | 
							return dir;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -350,10 +365,8 @@ GetHomeDir() noexcept
 | 
				
			|||||||
	if (const auto home = getenv("HOME");
 | 
						if (const auto home = getenv("HOME");
 | 
				
			||||||
	    IsValidPathString(home) && IsValidDir(home))
 | 
						    IsValidPathString(home) && IsValidDir(home))
 | 
				
			||||||
		return AllocatedPath::FromFS(home);
 | 
							return AllocatedPath::FromFS(home);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (PasswdEntry pw; pw.ReadByUid(getuid()))
 | 
					 | 
				
			||||||
		return SafePathFromFS(pw->pw_dir);
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nullptr;
 | 
						return nullptr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,13 @@ GetUserMusicDir() noexcept;
 | 
				
			|||||||
AllocatedPath
 | 
					AllocatedPath
 | 
				
			||||||
GetUserCacheDir() noexcept;
 | 
					GetUserCacheDir() noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Obtains cache directory for this application.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					[[gnu::const]]
 | 
				
			||||||
 | 
					AllocatedPath
 | 
				
			||||||
 | 
					GetAppCacheDir() noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Obtains the runtime directory for the current user.
 | 
					 * Obtains the runtime directory for the current user.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,9 +101,17 @@ AsyncInputStream::Seek(std::unique_lock<Mutex> &lock,
 | 
				
			|||||||
	assert(IsReady());
 | 
						assert(IsReady());
 | 
				
			||||||
	assert(seek_state == SeekState::NONE);
 | 
						assert(seek_state == SeekState::NONE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (new_offset == offset)
 | 
						if (new_offset == offset) {
 | 
				
			||||||
		/* no-op */
 | 
							/* no-op, but if the stream is not open anymore (maybe
 | 
				
			||||||
 | 
							   because it has failed), nothing can be read, so we
 | 
				
			||||||
 | 
							   should check for errors here instead of pretending
 | 
				
			||||||
 | 
							   everything's fine */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!open)
 | 
				
			||||||
 | 
								Check();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!IsSeekable())
 | 
						if (!IsSeekable())
 | 
				
			||||||
		throw std::runtime_error("Not seekable");
 | 
							throw std::runtime_error("Not seekable");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,6 +45,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#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;
 | 
				
			||||||
@@ -65,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream {
 | 
				
			|||||||
		 lsn_from(_lsn_from),
 | 
							 lsn_from(_lsn_from),
 | 
				
			||||||
		 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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -417,7 +417,6 @@ CurlInputStream::InitEasy()
 | 
				
			|||||||
	request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
 | 
						request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
 | 
				
			||||||
	request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
 | 
						request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
 | 
				
			||||||
	request->SetOption(CURLOPT_MAXREDIRS, 5L);
 | 
						request->SetOption(CURLOPT_MAXREDIRS, 5L);
 | 
				
			||||||
	request->SetOption(CURLOPT_FAILONERROR, 1L);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* this option eliminates the probe request when
 | 
						/* this option eliminates the probe request when
 | 
				
			||||||
	   username/password are specified */
 | 
						   username/password are specified */
 | 
				
			||||||
@@ -439,6 +438,14 @@ CurlInputStream::InitEasy()
 | 
				
			|||||||
	request->SetVerifyPeer(verify_peer);
 | 
						request->SetVerifyPeer(verify_peer);
 | 
				
			||||||
	request->SetVerifyHost(verify_host);
 | 
						request->SetVerifyHost(verify_host);
 | 
				
			||||||
	request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
 | 
						request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							request->SetProxyVerifyPeer(verify_peer);
 | 
				
			||||||
 | 
							request->SetProxyVerifyHost(verify_host);
 | 
				
			||||||
 | 
						} catch (...) {
 | 
				
			||||||
 | 
							/* these methods fail if libCURL was compiled with
 | 
				
			||||||
 | 
							   CURL_DISABLE_PROXY; ignore silently */
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,8 @@
 | 
				
			|||||||
#include "io/UniqueFileDescriptor.hxx"
 | 
					#include "io/UniqueFileDescriptor.hxx"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Path;
 | 
					class Path;
 | 
				
			||||||
class FileInfo;
 | 
					class FileInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept
 | 
				
			|||||||
AllocatedPath
 | 
					AllocatedPath
 | 
				
			||||||
Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
 | 
					Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	assert(env != nullptr);
 | 
					 | 
				
			||||||
	assert(_file != nullptr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	LocalObject file(env, _file);
 | 
						LocalObject file(env, _file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const jstring path = GetAbsolutePath(env, file);
 | 
						const jstring path = GetAbsolutePath(env, file);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,13 +18,13 @@ endif
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
conf.set('HAVE_MD5', crypto_md5_dep.found())
 | 
					conf.set('HAVE_MD5', crypto_md5_dep.found())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if libavutil_dep.found()
 | 
					if ffmpeg_util_dep.found()
 | 
				
			||||||
  crypto_base64 = static_library(
 | 
					  crypto_base64 = static_library(
 | 
				
			||||||
    'crypto_base64',
 | 
					    'crypto_base64',
 | 
				
			||||||
    'Base64.cxx',
 | 
					    'Base64.cxx',
 | 
				
			||||||
    include_directories: inc,
 | 
					    include_directories: inc,
 | 
				
			||||||
    dependencies: [
 | 
					    dependencies: [
 | 
				
			||||||
      libavutil_dep,
 | 
					      ffmpeg_util_dep,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -186,10 +186,6 @@ public:
 | 
				
			|||||||
		SetOption(CURLOPT_POSTFIELDSIZE, (long)size);
 | 
							SetOption(CURLOPT_POSTFIELDSIZE, (long)size);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void SetHttpPost(const struct curl_httppost *post) {
 | 
					 | 
				
			||||||
		SetOption(CURLOPT_HTTPPOST, post);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	template<typename T>
 | 
						template<typename T>
 | 
				
			||||||
	bool GetInfo(CURLINFO info, T value_r) const noexcept {
 | 
						bool GetInfo(CURLINFO info, T value_r) const noexcept {
 | 
				
			||||||
		return ::curl_easy_getinfo(handle, info, value_r) == CURLE_OK;
 | 
							return ::curl_easy_getinfo(handle, info, value_r) == CURLE_OK;
 | 
				
			||||||
@@ -199,10 +195,10 @@ public:
 | 
				
			|||||||
	 * Returns the response body's size, or -1 if that is unknown.
 | 
						 * Returns the response body's size, or -1 if that is unknown.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	[[gnu::pure]]
 | 
						[[gnu::pure]]
 | 
				
			||||||
	int64_t GetContentLength() const noexcept {
 | 
						curl_off_t GetContentLength() const noexcept {
 | 
				
			||||||
		double value;
 | 
							curl_off_t value;
 | 
				
			||||||
		return GetInfo(CURLINFO_CONTENT_LENGTH_DOWNLOAD, &value)
 | 
							return GetInfo(CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &value)
 | 
				
			||||||
			? (int64_t)value
 | 
								? value
 | 
				
			||||||
			: -1;
 | 
								: -1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -123,6 +123,14 @@ public:
 | 
				
			|||||||
		easy.SetVerifyPeer(value);
 | 
							easy.SetVerifyPeer(value);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void SetProxyVerifyHost(bool value) {
 | 
				
			||||||
 | 
							easy.SetOption(CURLOPT_PROXY_SSL_VERIFYHOST, value ? 2L : 0L);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void SetProxyVerifyPeer(bool value) {
 | 
				
			||||||
 | 
							easy.SetOption(CURLOPT_PROXY_SSL_VERIFYPEER, value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void SetNoBody(bool value=true) {
 | 
						void SetNoBody(bool value=true) {
 | 
				
			||||||
		easy.SetNoBody(value);
 | 
							easy.SetNoBody(value);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
curl_dep = dependency('libcurl', version: '>= 7.33', required: get_option('curl'))
 | 
					curl_dep = dependency('libcurl', version: '>= 7.55', required: get_option('curl'))
 | 
				
			||||||
conf.set('ENABLE_CURL', curl_dep.found())
 | 
					conf.set('ENABLE_CURL', curl_dep.found())
 | 
				
			||||||
if not curl_dep.found()
 | 
					if not curl_dep.found()
 | 
				
			||||||
  subdir_done()
 | 
					  subdir_done()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,7 @@
 | 
				
			|||||||
#include "Iter.hxx"
 | 
					#include "Iter.hxx"
 | 
				
			||||||
#include "Values.hxx"
 | 
					#include "Values.hxx"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
#include <stdexcept>
 | 
					#include <stdexcept>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ODBus {
 | 
					namespace ODBus {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
 * Copyright 2003-2021 The Music Player Daemon Project
 | 
					 * Copyright 2003-2022 The Music Player Daemon Project
 | 
				
			||||||
 * http://www.musicpd.org
 | 
					 * http://www.musicpd.org
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * This program is free software; you can redistribute it and/or modify
 | 
					 * This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
@@ -17,26 +17,23 @@
 | 
				
			|||||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
					 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "LogError.hxx"
 | 
					#pragma once
 | 
				
			||||||
#include "Domain.hxx"
 | 
					 | 
				
			||||||
#include "Log.hxx"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern "C" {
 | 
					extern "C" {
 | 
				
			||||||
#include <libavutil/error.h>
 | 
					#include <libavutil/samplefmt.h>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					#include <fmt/format.h>
 | 
				
			||||||
LogFfmpegError(int errnum)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	char msg[256];
 | 
					 | 
				
			||||||
	av_strerror(errnum, msg, sizeof(msg));
 | 
					 | 
				
			||||||
	LogError(ffmpeg_domain, msg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					template<>
 | 
				
			||||||
LogFfmpegError(int errnum, const char *prefix)
 | 
					struct fmt::formatter<AVSampleFormat> : formatter<string_view>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	char msg[256];
 | 
						template<typename FormatContext>
 | 
				
			||||||
	av_strerror(errnum, msg, sizeof(msg));
 | 
						auto format(const AVSampleFormat format, FormatContext &ctx) {
 | 
				
			||||||
	FmtError(ffmpeg_domain, "{}: {}", prefix, msg);
 | 
							const char *name = av_get_sample_fmt_name(format);
 | 
				
			||||||
}
 | 
							if (name == nullptr)
 | 
				
			||||||
 | 
								name = "?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return formatter<string_view>::format(name, ctx);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,29 +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_FFMPEG_LOG_ERROR_HXX
 | 
					 | 
				
			||||||
#define MPD_FFMPEG_LOG_ERROR_HXX
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void
 | 
					 | 
				
			||||||
LogFfmpegError(int errnum);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void
 | 
					 | 
				
			||||||
LogFfmpegError(int errnum, const char *prefix);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
@@ -13,6 +13,29 @@ else
 | 
				
			|||||||
endif
 | 
					endif
 | 
				
			||||||
conf.set('HAVE_LIBAVFILTER', libavfilter_dep.found())
 | 
					conf.set('HAVE_LIBAVFILTER', libavfilter_dep.found())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if not libavutil_dep.found()
 | 
				
			||||||
 | 
					  ffmpeg_util_dep = dependency('', required: false)
 | 
				
			||||||
 | 
					  ffmpeg_dep = dependency('', required: false)
 | 
				
			||||||
 | 
					  subdir_done()
 | 
				
			||||||
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ffmpeg_util = static_library(
 | 
				
			||||||
 | 
					  'ffmpeg_util',
 | 
				
			||||||
 | 
					  'Interleave.cxx',
 | 
				
			||||||
 | 
					  'Error.cxx',
 | 
				
			||||||
 | 
					  include_directories: inc,
 | 
				
			||||||
 | 
					  dependencies: [
 | 
				
			||||||
 | 
					    libavutil_dep,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ffmpeg_util_dep = declare_dependency(
 | 
				
			||||||
 | 
					  link_with: ffmpeg_util,
 | 
				
			||||||
 | 
					  dependencies: [
 | 
				
			||||||
 | 
					    libavutil_dep,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if not enable_ffmpeg
 | 
					if not enable_ffmpeg
 | 
				
			||||||
  ffmpeg_dep = dependency('', required: false)
 | 
					  ffmpeg_dep = dependency('', required: false)
 | 
				
			||||||
  subdir_done()
 | 
					  subdir_done()
 | 
				
			||||||
@@ -30,17 +53,16 @@ ffmpeg = static_library(
 | 
				
			|||||||
  'ffmpeg',
 | 
					  'ffmpeg',
 | 
				
			||||||
  'Init.cxx',
 | 
					  'Init.cxx',
 | 
				
			||||||
  'Interleave.cxx',
 | 
					  'Interleave.cxx',
 | 
				
			||||||
  'LogError.cxx',
 | 
					 | 
				
			||||||
  'LogCallback.cxx',
 | 
					  'LogCallback.cxx',
 | 
				
			||||||
  'Error.cxx',
 | 
					  'Error.cxx',
 | 
				
			||||||
  'Domain.cxx',
 | 
					  'Domain.cxx',
 | 
				
			||||||
  ffmpeg_sources,
 | 
					  ffmpeg_sources,
 | 
				
			||||||
  include_directories: inc,
 | 
					  include_directories: inc,
 | 
				
			||||||
  dependencies: [
 | 
					  dependencies: [
 | 
				
			||||||
 | 
					    ffmpeg_util_dep,
 | 
				
			||||||
    libavformat_dep,
 | 
					    libavformat_dep,
 | 
				
			||||||
    libavcodec_dep,
 | 
					    libavcodec_dep,
 | 
				
			||||||
    libavfilter_dep,
 | 
					    libavfilter_dep,
 | 
				
			||||||
    libavutil_dep,
 | 
					 | 
				
			||||||
    log_dep,
 | 
					    log_dep,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -48,9 +70,9 @@ ffmpeg = static_library(
 | 
				
			|||||||
ffmpeg_dep = declare_dependency(
 | 
					ffmpeg_dep = declare_dependency(
 | 
				
			||||||
  link_with: ffmpeg,
 | 
					  link_with: ffmpeg,
 | 
				
			||||||
  dependencies: [
 | 
					  dependencies: [
 | 
				
			||||||
 | 
					    ffmpeg_util_dep,
 | 
				
			||||||
    libavformat_dep,
 | 
					    libavformat_dep,
 | 
				
			||||||
    libavcodec_dep,
 | 
					    libavcodec_dep,
 | 
				
			||||||
    libavfilter_dep,
 | 
					    libavfilter_dep,
 | 
				
			||||||
    libavutil_dep,
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,17 +18,25 @@ if icu_dep.found()
 | 
				
			|||||||
    'Util.cxx',
 | 
					    'Util.cxx',
 | 
				
			||||||
    'Init.cxx',
 | 
					    'Init.cxx',
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
elif not get_option('iconv').disabled()
 | 
					else
 | 
				
			||||||
  # an installed iconv library will make the builtin iconv() unavailable,
 | 
					  if meson.version().version_compare('>= 0.60')
 | 
				
			||||||
  # so search for the library first and pass it as (possible) dependency
 | 
					    iconv_dep = dependency('iconv', required: get_option('iconv'))
 | 
				
			||||||
  iconv_dep = compiler.find_library('libiconv', required: false)
 | 
					    conf.set('HAVE_ICONV', iconv_dep.found())
 | 
				
			||||||
  have_iconv = compiler.has_function('iconv', 
 | 
					  elif not get_option('iconv').disabled()
 | 
				
			||||||
    dependencies: iconv_dep, 
 | 
					    iconv_open_snippet = '''#include <iconv.h>
 | 
				
			||||||
    prefix : '#include <iconv.h>')
 | 
					      int main() {
 | 
				
			||||||
  if not have_iconv and get_option('iconv').enabled()
 | 
					      iconv_open("","");
 | 
				
			||||||
    error('iconv() not available')
 | 
					      }'''
 | 
				
			||||||
 | 
					    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()
 | 
				
			||||||
 | 
					      error('iconv() not available')
 | 
				
			||||||
 | 
					    endif
 | 
				
			||||||
 | 
					    conf.set('HAVE_ICONV', have_iconv)
 | 
				
			||||||
  endif
 | 
					  endif
 | 
				
			||||||
  conf.set('HAVE_ICONV', have_iconv)
 | 
					 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
icu = static_library(
 | 
					icu = static_library(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,8 +52,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
 | 
				
			|||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					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;
 | 
				
			||||||
@@ -81,19 +62,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void
 | 
					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);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	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);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -188,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);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,7 +92,7 @@ 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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -103,7 +103,7 @@ mixer_get_volume(Mixer *mixer)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert(mixer != nullptr);
 | 
						assert(mixer != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (mixer->plugin.global && !mixer->failure)
 | 
						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);
 | 
				
			||||||
@@ -128,7 +128,7 @@ mixer_set_volume(Mixer *mixer, unsigned volume)
 | 
				
			|||||||
	assert(mixer != nullptr);
 | 
						assert(mixer != nullptr);
 | 
				
			||||||
	assert(volume <= 100);
 | 
						assert(volume <= 100);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (mixer->plugin.global && !mixer->failure)
 | 
						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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,55 +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;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Throws on error.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
void
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -812,8 +812,12 @@ AlsaOutput::Open(AudioFormat &audio_format)
 | 
				
			|||||||
				      fmt::format("Failed to open ALSA device \"{}\"",
 | 
									      fmt::format("Failed to open ALSA device \"{}\"",
 | 
				
			||||||
						  GetDevice()).c_str());
 | 
											  GetDevice()).c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const char *pcm_name = snd_pcm_name(pcm);
 | 
				
			||||||
 | 
						if (pcm_name == nullptr)
 | 
				
			||||||
 | 
							pcm_name = "?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	FmtDebug(alsa_output_domain, "opened {} type={}",
 | 
						FmtDebug(alsa_output_domain, "opened {} type={}",
 | 
				
			||||||
		 snd_pcm_name(pcm),
 | 
							 pcm_name,
 | 
				
			||||||
		 snd_pcm_type_name(snd_pcm_type(pcm)));
 | 
							 snd_pcm_type_name(snd_pcm_type(pcm)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ENABLE_DSD
 | 
					#ifdef ENABLE_DSD
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
		kAudioObjectPropertyElementMain,
 | 
							KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static constexpr AudioObjectPropertyAddress default_output_device{
 | 
						static constexpr AudioObjectPropertyAddress default_output_device{
 | 
				
			||||||
		kAudioHardwarePropertyDefaultOutputDevice,
 | 
							kAudioHardwarePropertyDefaultOutputDevice,
 | 
				
			||||||
		kAudioObjectPropertyScopeOutput,
 | 
							kAudioObjectPropertyScopeOutput,
 | 
				
			||||||
		kAudioObjectPropertyElementMain
 | 
							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_VirtualMainVolume,
 | 
							KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
 | 
				
			||||||
		kAudioObjectPropertyScopeOutput,
 | 
							kAudioObjectPropertyScopeOutput,
 | 
				
			||||||
		kAudioObjectPropertyElementMain,
 | 
							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_VirtualMainVolume,
 | 
							KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
 | 
				
			||||||
		kAudioObjectPropertyScopeOutput,
 | 
							kAudioObjectPropertyScopeOutput,
 | 
				
			||||||
		kAudioObjectPropertyElementMain
 | 
							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,
 | 
				
			||||||
		kAudioObjectPropertyElementMain
 | 
							KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
 | 
						static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
 | 
				
			||||||
		kAudioStreamPropertyDirection,
 | 
							kAudioStreamPropertyDirection,
 | 
				
			||||||
		kAudioObjectPropertyScopeOutput,
 | 
							kAudioObjectPropertyScopeOutput,
 | 
				
			||||||
		kAudioObjectPropertyElementMain
 | 
							KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
 | 
						static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
 | 
				
			||||||
		kAudioStreamPropertyAvailablePhysicalFormats,
 | 
							kAudioStreamPropertyAvailablePhysicalFormats,
 | 
				
			||||||
		kAudioObjectPropertyScopeOutput,
 | 
							kAudioObjectPropertyScopeOutput,
 | 
				
			||||||
		kAudioObjectPropertyElementMain
 | 
							KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
 | 
						static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
 | 
				
			||||||
		kAudioStreamPropertyPhysicalFormat,
 | 
							kAudioStreamPropertyPhysicalFormat,
 | 
				
			||||||
		kAudioObjectPropertyScopeOutput,
 | 
							kAudioObjectPropertyScopeOutput,
 | 
				
			||||||
		kAudioObjectPropertyElementMain
 | 
							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,
 | 
				
			||||||
		kAudioObjectPropertyElementMain
 | 
							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,
 | 
				
			||||||
		kAudioObjectPropertyElementMain,
 | 
							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,
 | 
				
			||||||
		kAudioObjectPropertyElementMain,
 | 
							KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto ids =
 | 
						const auto ids =
 | 
				
			||||||
@@ -618,7 +627,7 @@ osx_render(void *vdata,
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	OSXOutput *od = (OSXOutput *) vdata;
 | 
						OSXOutput *od = (OSXOutput *) vdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int count = in_number_frames * od->asbd.mBytesPerFrame;
 | 
						std::size_t count = in_number_frames * od->asbd.mBytesPerFrame;
 | 
				
			||||||
	buffer_list->mBuffers[0].mDataByteSize =
 | 
						buffer_list->mBuffers[0].mDataByteSize =
 | 
				
			||||||
		od->ring_buffer->pop((uint8_t *)buffer_list->mBuffers[0].mData,
 | 
							od->ring_buffer->pop((uint8_t *)buffer_list->mBuffers[0].mData,
 | 
				
			||||||
				     count);
 | 
									     count);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -514,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);
 | 
				
			||||||
@@ -522,7 +523,13 @@ PipeWireOutput::Open(AudioFormat &audio_format)
 | 
				
			|||||||
		pw_properties_setf(props, PW_KEY_REMOTE_NAME, "%s", remote);
 | 
							pw_properties_setf(props, PW_KEY_REMOTE_NAME, "%s", remote);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (target != nullptr && target_id == PW_ID_ANY)
 | 
						if (target != nullptr && target_id == PW_ID_ANY)
 | 
				
			||||||
		pw_properties_setf(props, PW_KEY_NODE_TARGET, "%s", target);
 | 
							pw_properties_setf(props,
 | 
				
			||||||
 | 
					#if PW_CHECK_VERSION(0, 3, 64)
 | 
				
			||||||
 | 
									   PW_KEY_TARGET_OBJECT,
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
									   PW_KEY_NODE_TARGET,
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
									   "%s", target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef PW_KEY_NODE_RATE
 | 
					#ifdef PW_KEY_NODE_RATE
 | 
				
			||||||
	/* ask PipeWire to change the graph sample rate to ours
 | 
						/* ask PipeWire to change the graph sample rate to ours
 | 
				
			||||||
@@ -966,6 +973,8 @@ PipeWireOutput::SendTag(const Tag &tag)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	struct spa_dict dict = SPA_DICT_INIT(items, n_items);
 | 
						struct spa_dict dict = SPA_DICT_INIT(items, n_items);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const PipeWire::ThreadLoopLock lock(thread_loop);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto rc = pw_stream_update_properties(stream, &dict);
 | 
						auto rc = pw_stream_update_properties(stream, &dict);
 | 
				
			||||||
	if (rc < 0)
 | 
						if (rc < 0)
 | 
				
			||||||
		LogWarning(pipewire_output_domain, "Error updating properties");
 | 
							LogWarning(pipewire_output_domain, "Error updating properties");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,6 @@ storage_glue = static_library(
 | 
				
			|||||||
  'CompositeStorage.cxx',
 | 
					  'CompositeStorage.cxx',
 | 
				
			||||||
  'MemoryDirectoryReader.cxx',
 | 
					  'MemoryDirectoryReader.cxx',
 | 
				
			||||||
  'Configured.cxx',
 | 
					  'Configured.cxx',
 | 
				
			||||||
  'StorageState.cxx',
 | 
					 | 
				
			||||||
  include_directories: inc,
 | 
					  include_directories: inc,
 | 
				
			||||||
  dependencies: [
 | 
					  dependencies: [
 | 
				
			||||||
    boost_dep,
 | 
					    boost_dep,
 | 
				
			||||||
@@ -31,4 +30,3 @@ storage_glue_dep = declare_dependency(
 | 
				
			|||||||
    storage_plugins_dep,
 | 
					    storage_plugins_dep,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@
 | 
				
			|||||||
#include "storage/StorageInterface.hxx"
 | 
					#include "storage/StorageInterface.hxx"
 | 
				
			||||||
#include "storage/FileInfo.hxx"
 | 
					#include "storage/FileInfo.hxx"
 | 
				
			||||||
#include "storage/MemoryDirectoryReader.hxx"
 | 
					#include "storage/MemoryDirectoryReader.hxx"
 | 
				
			||||||
 | 
					#include "lib/curl/Error.hxx"
 | 
				
			||||||
#include "lib/curl/Init.hxx"
 | 
					#include "lib/curl/Init.hxx"
 | 
				
			||||||
#include "lib/curl/Global.hxx"
 | 
					#include "lib/curl/Global.hxx"
 | 
				
			||||||
#include "lib/curl/Slist.hxx"
 | 
					#include "lib/curl/Slist.hxx"
 | 
				
			||||||
@@ -34,7 +35,6 @@
 | 
				
			|||||||
#include "event/InjectEvent.hxx"
 | 
					#include "event/InjectEvent.hxx"
 | 
				
			||||||
#include "thread/Mutex.hxx"
 | 
					#include "thread/Mutex.hxx"
 | 
				
			||||||
#include "thread/Cond.hxx"
 | 
					#include "thread/Cond.hxx"
 | 
				
			||||||
#include "time/Parser.hxx"
 | 
					 | 
				
			||||||
#include "util/ASCII.hxx"
 | 
					#include "util/ASCII.hxx"
 | 
				
			||||||
#include "util/RuntimeError.hxx"
 | 
					#include "util/RuntimeError.hxx"
 | 
				
			||||||
#include "util/StringCompare.hxx"
 | 
					#include "util/StringCompare.hxx"
 | 
				
			||||||
@@ -171,8 +171,9 @@ struct DavResponse {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
static unsigned
 | 
					static unsigned
 | 
				
			||||||
ParseStatus(const char *s)
 | 
					ParseStatus(const char *s) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	/* skip the "HTTP/1.1" prefix */
 | 
						/* skip the "HTTP/1.1" prefix */
 | 
				
			||||||
	const char *space = std::strchr(s, ' ');
 | 
						const char *space = std::strchr(s, ' ');
 | 
				
			||||||
@@ -182,37 +183,37 @@ ParseStatus(const char *s)
 | 
				
			|||||||
	return strtoul(space + 1, nullptr, 10);
 | 
						return strtoul(space + 1, nullptr, 10);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
static unsigned
 | 
					static unsigned
 | 
				
			||||||
ParseStatus(const char *s, size_t length)
 | 
					ParseStatus(const char *s, size_t length) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return ParseStatus(std::string(s, length).c_str());
 | 
						return ParseStatus(std::string(s, length).c_str());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
static std::chrono::system_clock::time_point
 | 
					static std::chrono::system_clock::time_point
 | 
				
			||||||
ParseTimeStamp(const char *s)
 | 
					ParseTimeStamp(const char *s) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	try {
 | 
						return std::chrono::system_clock::from_time_t(curl_getdate(s, nullptr));
 | 
				
			||||||
		// TODO: make this more robust
 | 
					 | 
				
			||||||
		return ParseTimePoint(s, "%a, %d %b %Y %T");
 | 
					 | 
				
			||||||
	} catch (...) {
 | 
					 | 
				
			||||||
		return std::chrono::system_clock::time_point::min();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
static std::chrono::system_clock::time_point
 | 
					static std::chrono::system_clock::time_point
 | 
				
			||||||
ParseTimeStamp(const char *s, size_t length)
 | 
					ParseTimeStamp(const char *s, size_t length) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return ParseTimeStamp(std::string(s, length).c_str());
 | 
						return ParseTimeStamp(std::string(s, length).c_str());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
static uint64_t
 | 
					static uint64_t
 | 
				
			||||||
ParseU64(const char *s)
 | 
					ParseU64(const char *s) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return strtoull(s, nullptr, 10);
 | 
						return strtoull(s, nullptr, 10);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[gnu::pure]]
 | 
				
			||||||
static uint64_t
 | 
					static uint64_t
 | 
				
			||||||
ParseU64(const char *s, size_t length)
 | 
					ParseU64(const char *s, size_t length) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return ParseU64(std::string(s, length).c_str());
 | 
						return ParseU64(std::string(s, length).c_str());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -278,6 +279,7 @@ public:
 | 
				
			|||||||
				  "<a:resourcetype/>"
 | 
									  "<a:resourcetype/>"
 | 
				
			||||||
				  "<a:getcontenttype/>"
 | 
									  "<a:getcontenttype/>"
 | 
				
			||||||
				  "<a:getcontentlength/>"
 | 
									  "<a:getcontentlength/>"
 | 
				
			||||||
 | 
									  "<a:getlastmodified/>"
 | 
				
			||||||
				  "</a:prop>"
 | 
									  "</a:prop>"
 | 
				
			||||||
				  "</a:propfind>");
 | 
									  "</a:propfind>");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -299,8 +301,9 @@ private:
 | 
				
			|||||||
	/* virtual methods from CurlResponseHandler */
 | 
						/* virtual methods from CurlResponseHandler */
 | 
				
			||||||
	void OnHeaders(unsigned status, Curl::Headers &&headers) final {
 | 
						void OnHeaders(unsigned status, Curl::Headers &&headers) final {
 | 
				
			||||||
		if (status != 207)
 | 
							if (status != 207)
 | 
				
			||||||
			throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
 | 
								throw HttpStatusError(status,
 | 
				
			||||||
						 status);
 | 
										      StringFormat<80>("Status %u from WebDAV server; expected \"207 Multi-Status\"",
 | 
				
			||||||
 | 
												       status).c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!IsXmlContentType(headers))
 | 
							if (!IsXmlContentType(headers))
 | 
				
			||||||
			throw std::runtime_error("Unexpected Content-Type from WebDAV server");
 | 
								throw std::runtime_error("Unexpected Content-Type from WebDAV server");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,8 +70,11 @@ FormatLastError(DWORD code, const char *fmt, Args&&... args) noexcept
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	char buffer[512];
 | 
						char buffer[512];
 | 
				
			||||||
	const auto end = buffer + sizeof(buffer);
 | 
						const auto end = buffer + sizeof(buffer);
 | 
				
			||||||
	size_t length = snprintf(buffer, sizeof(buffer) - 128,
 | 
						constexpr std::size_t max_prefix = sizeof(buffer) - 128;
 | 
				
			||||||
 | 
						size_t length = snprintf(buffer, max_prefix,
 | 
				
			||||||
				 fmt, std::forward<Args>(args)...);
 | 
									 fmt, std::forward<Args>(args)...);
 | 
				
			||||||
 | 
						if (length >= max_prefix)
 | 
				
			||||||
 | 
							length = max_prefix - 1;
 | 
				
			||||||
	char *p = buffer + length;
 | 
						char *p = buffer + length;
 | 
				
			||||||
	*p++ = ':';
 | 
						*p++ = ':';
 | 
				
			||||||
	*p++ = ' ';
 | 
						*p++ = ' ';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -263,8 +263,14 @@ TagBuilder::RemoveAll() noexcept
 | 
				
			|||||||
void
 | 
					void
 | 
				
			||||||
TagBuilder::RemoveType(TagType type) noexcept
 | 
					TagBuilder::RemoveType(TagType type) noexcept
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						if (items.empty())
 | 
				
			||||||
 | 
							/* don't acquire the tag_pool_lock if we're not going
 | 
				
			||||||
 | 
							   to call tag_pool_put_item() anyway */
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto begin = items.begin(), end = items.end();
 | 
						const auto begin = items.begin(), end = items.end();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const std::scoped_lock<Mutex> protect(tag_pool_lock);
 | 
				
			||||||
	items.erase(std::remove_if(begin, end,
 | 
						items.erase(std::remove_if(begin, end,
 | 
				
			||||||
				   [type](TagItem *item) {
 | 
									   [type](TagItem *item) {
 | 
				
			||||||
					   if (item->type != type)
 | 
										   if (item->type != type)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,57 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2014-2019 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 "Parser.hxx"
 | 
					 | 
				
			||||||
#include "Convert.hxx"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <cassert>
 | 
					 | 
				
			||||||
#include <stdexcept>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <time.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::chrono::system_clock::time_point
 | 
					 | 
				
			||||||
ParseTimePoint(const char *s, const char *format)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	assert(s != nullptr);
 | 
					 | 
				
			||||||
	assert(format != nullptr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef _WIN32
 | 
					 | 
				
			||||||
	/* TODO: emulate strptime()? */
 | 
					 | 
				
			||||||
	(void)s;
 | 
					 | 
				
			||||||
	(void)format;
 | 
					 | 
				
			||||||
	throw std::runtime_error("Time parsing not implemented on Windows");
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
	struct tm tm{};
 | 
					 | 
				
			||||||
	const char *end = strptime(s, format, &tm);
 | 
					 | 
				
			||||||
	if (end == nullptr || *end != 0)
 | 
					 | 
				
			||||||
		throw std::runtime_error("Failed to parse time stamp");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return TimeGm(tm);
 | 
					 | 
				
			||||||
#endif /* !_WIN32 */
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,43 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2014-2019 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.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef TIME_PARSER_HXX
 | 
					 | 
				
			||||||
#define TIME_PARSER_HXX
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <chrono>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Parse a time stamp.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Throws std::runtime_error on error.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
std::chrono::system_clock::time_point
 | 
					 | 
				
			||||||
ParseTimePoint(const char *s, const char *format);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user