release v0.22.10
-----BEGIN PGP SIGNATURE----- iQJEBAABCgAuFiEEA5IzWngIOJSkMBxDI26KWMbbRRIFAmENYHwQHG1heEBtdXNp Y3BkLm9yZwAKCRAjbopYxttFEpGtD/9ToU27x36NAAFpChicSqbu3h2wtJ29lowT ivV80XB26pQeGK0DSXADSs38MVXo42i3vqg0zGWV9TRbcDs5VErXANVLN16qsKCu U0v1BDY11UiYp6ATiUjIahyG1UsmbRVZlfDyVIhYvmFpGLFw6+03HH6w2k/v85ns FxyXkYDYcUokPJPEQcChE1eIwKsGM6MjbdVIkJAEo3wGhL/Hhy2KUskrFcCo1PDj 7xxGrwauG+8wSjAWMA5vzl3udRaDauuXztm5QbQIDdsbRaCiBAdgkzC0PvIxTOr0 bR4WHVB0KSiM96yIXNtg/WZxO0XrxppmX/E4eZSgz0JGKMrAHcoTJAUCIDu3X719 gJnJLg7r2X5dTchXezv09YoJolKbw1bOooyAuE4FCDWMsOa2GRuBZC+8w7DNTZuo PTh+Z40fnfpNBofe+e/WZrXr6i2TKk8CqHEidq2GHlOkvTR7g6m8MjOLvZNHotMb 9ECr9MhzXH+nvEX8IaxvjWsfLJiDbUgnVsQ+6akGtkbragaDN/Wgr/XdkELoLlsK LZFY5ngnZmDXOu4tjBLJtfrTkZB2/Hld4xtF1qlsy9fvZBRKeKlpABTLaa8r/vnR Ta0rB2O3/dculZbHZRUnZvLS4Xv4g322vW9Wso9IBoWRn9fC8b2zR93k7breAqcn pKbYGg/j6g== =/954 -----END PGP SIGNATURE----- Merge tag 'v0.22.10' release v0.22.10
This commit is contained in:
commit
bd893e6336
7
NEWS
7
NEWS
@ -17,18 +17,23 @@ ver 0.23 (not yet released)
|
|||||||
- new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location"
|
- new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location"
|
||||||
* new build-time dependency: libfmt
|
* new build-time dependency: libfmt
|
||||||
|
|
||||||
ver 0.22.10 (not yet released)
|
ver 0.22.10 (2021/08/06)
|
||||||
* protocol
|
* protocol
|
||||||
- support "albumart" for virtual tracks in CUE sheets
|
- support "albumart" for virtual tracks in CUE sheets
|
||||||
* database
|
* database
|
||||||
- simple: fix crash bug
|
- simple: fix crash bug
|
||||||
|
- simple: fix absolute paths in CUE "as_directory" entries
|
||||||
|
- simple: prune CUE entries from database for non-existent songs
|
||||||
* input
|
* input
|
||||||
- curl: fix crash bug after stream with Icy metadata was closed by peer
|
- curl: fix crash bug after stream with Icy metadata was closed by peer
|
||||||
- tidal: remove defunct unmaintained plugin
|
- tidal: remove defunct unmaintained plugin
|
||||||
* tags
|
* tags
|
||||||
- fix crash caused by bug in TagBuilder and a few potential reference leaks
|
- fix crash caused by bug in TagBuilder and a few potential reference leaks
|
||||||
* output
|
* output
|
||||||
|
- httpd: fix missing tag after seeking into a new song
|
||||||
- oss: fix channel order of multi-channel files
|
- oss: fix channel order of multi-channel files
|
||||||
|
* mixer
|
||||||
|
- alsa: fix yet more rounding errors
|
||||||
|
|
||||||
ver 0.22.9 (2021/06/23)
|
ver 0.22.9 (2021/06/23)
|
||||||
* database
|
* database
|
||||||
|
@ -91,8 +91,9 @@ class AndroidNdkToolchain:
|
|||||||
|
|
||||||
self.arch = arch
|
self.arch = arch
|
||||||
self.install_prefix = install_prefix
|
self.install_prefix = install_prefix
|
||||||
|
self.toolchain_arch = abi_info['toolchain_arch']
|
||||||
|
|
||||||
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
|
toolchain_path = os.path.join(ndk_path, 'toolchains', self.toolchain_arch + '-' + gcc_version, 'prebuilt', build_arch)
|
||||||
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
|
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
|
||||||
llvm_triple = abi_info['llvm_triple'] + android_api_level
|
llvm_triple = abi_info['llvm_triple'] + android_api_level
|
||||||
|
|
||||||
|
@ -1257,7 +1257,7 @@ This plugin requires building with ``libavfilter`` (FFmpeg).
|
|||||||
* - **graph "..."**
|
* - **graph "..."**
|
||||||
- Specifies the ``libavfilter`` graph; read the `FFmpeg
|
- Specifies the ``libavfilter`` graph; read the `FFmpeg
|
||||||
documentation
|
documentation
|
||||||
<https://libav.org/documentation/libavfilter.html#Filtergraph-syntax-1>`_
|
<https://ffmpeg.org/ffmpeg-filters.html#Filtergraph-syntax-1>`_
|
||||||
for details
|
for details
|
||||||
|
|
||||||
|
|
||||||
|
@ -388,14 +388,14 @@ ffmpeg = FfmpegProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
openssl = OpenSSLProject(
|
openssl = OpenSSLProject(
|
||||||
'https://www.openssl.org/source/openssl-3.0.0-alpha16.tar.gz',
|
'https://www.openssl.org/source/openssl-3.0.0-beta2.tar.gz',
|
||||||
'08ce8244b59d75f40f91170dfcb012bf25309cdcb1fef9502e39d694f883d1d1',
|
'e76ab22879201b12f014393ee4becec7f264d8f6955b1036839128002868df71',
|
||||||
'include/openssl/ossl_typ.h',
|
'include/openssl/ossl_typ.h',
|
||||||
)
|
)
|
||||||
|
|
||||||
curl = AutotoolsProject(
|
curl = AutotoolsProject(
|
||||||
'https://curl.se/download/curl-7.76.1.tar.xz',
|
'https://curl.se/download/curl-7.78.0.tar.xz',
|
||||||
'64bb5288c39f0840c07d077e30d9052e1cbb9fa6c2dc52523824cc859e679145',
|
'be42766d5664a739c3974ee3dfbbcbe978a4ccb1fe628bb1d9b59ac79e445fb5',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
|
@ -17,6 +17,12 @@ class OpenSSLProject(MakeProject):
|
|||||||
'build_libs',
|
'build_libs',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_make_install_args(self, toolchain):
|
||||||
|
# OpenSSL's Makefile runs "ranlib" during installation
|
||||||
|
return MakeProject.get_make_install_args(self, toolchain) + [
|
||||||
|
'RANLIB=' + toolchain.ranlib,
|
||||||
|
]
|
||||||
|
|
||||||
def build(self, toolchain):
|
def build(self, toolchain):
|
||||||
src = self.unpack(toolchain, out_of_tree=False)
|
src = self.unpack(toolchain, out_of_tree=False)
|
||||||
|
|
||||||
@ -42,6 +48,7 @@ class OpenSSLProject(MakeProject):
|
|||||||
}
|
}
|
||||||
|
|
||||||
openssl_arch = openssl_archs[toolchain.arch]
|
openssl_arch = openssl_archs[toolchain.arch]
|
||||||
|
cross_compile_prefix = toolchain.toolchain_arch + '-'
|
||||||
|
|
||||||
subprocess.check_call(['./Configure',
|
subprocess.check_call(['./Configure',
|
||||||
'no-shared',
|
'no-shared',
|
||||||
@ -50,6 +57,7 @@ class OpenSSLProject(MakeProject):
|
|||||||
'no-tests',
|
'no-tests',
|
||||||
'no-asm', # "asm" causes build failures on Windows
|
'no-asm', # "asm" causes build failures on Windows
|
||||||
openssl_arch,
|
openssl_arch,
|
||||||
|
'--cross-compile-prefix=' + cross_compile_prefix,
|
||||||
'--prefix=' + toolchain.install_prefix],
|
'--prefix=' + toolchain.install_prefix],
|
||||||
cwd=src, env=toolchain.env)
|
cwd=src, env=toolchain.env)
|
||||||
MakeProject.build(self, toolchain, src)
|
MakeProject.build(self, toolchain, src)
|
||||||
|
@ -20,7 +20,7 @@ class Project:
|
|||||||
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\d+)?)(\+.*)?$', self.base)
|
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)$', 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)
|
||||||
|
|
||||||
|
@ -109,6 +109,23 @@ Directory::FindChild(std::string_view name) const noexcept
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Directory::TargetExists(std::string_view _target) const noexcept
|
||||||
|
{
|
||||||
|
StringView target{_target};
|
||||||
|
|
||||||
|
if (target.SkipPrefix("../")) {
|
||||||
|
if (parent == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return parent->TargetExists(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sorry for the const_cast ... */
|
||||||
|
const auto lr = const_cast<Directory *>(this)->LookupDirectory(target);
|
||||||
|
return lr.directory->FindSong(lr.rest) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Directory::PruneEmpty() noexcept
|
Directory::PruneEmpty() noexcept
|
||||||
{
|
{
|
||||||
|
@ -118,13 +118,17 @@ public:
|
|||||||
return new Directory(std::string(), nullptr);
|
return new Directory(std::string(), nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsPlaylist() const noexcept {
|
||||||
|
return device == DEVICE_PLAYLIST;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this really a regular file which is being treated like a
|
* Is this really a regular file which is being treated like a
|
||||||
* directory?
|
* directory?
|
||||||
*/
|
*/
|
||||||
bool IsReallyAFile() const noexcept {
|
bool IsReallyAFile() const noexcept {
|
||||||
return device == DEVICE_INARCHIVE ||
|
return device == DEVICE_INARCHIVE ||
|
||||||
device == DEVICE_PLAYLIST ||
|
IsPlaylist() ||
|
||||||
device == DEVICE_CONTAINER;
|
device == DEVICE_CONTAINER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,11 +210,13 @@ public:
|
|||||||
* Looks up a directory by its relative URI.
|
* Looks up a directory by its relative URI.
|
||||||
*
|
*
|
||||||
* @param uri the relative URI
|
* @param uri the relative URI
|
||||||
* @return the Directory, or nullptr if none was found
|
|
||||||
*/
|
*/
|
||||||
gcc_pure
|
gcc_pure
|
||||||
LookupResult LookupDirectory(std::string_view uri) noexcept;
|
LookupResult LookupDirectory(std::string_view uri) noexcept;
|
||||||
|
|
||||||
|
[[gnu::pure]]
|
||||||
|
bool TargetExists(std::string_view target) const noexcept;
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool IsEmpty() const noexcept {
|
bool IsEmpty() const noexcept {
|
||||||
return children.empty() &&
|
return children.empty() &&
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "playlist/SongEnumerator.hxx"
|
#include "playlist/SongEnumerator.hxx"
|
||||||
#include "storage/FileInfo.hxx"
|
#include "storage/FileInfo.hxx"
|
||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
|
#include "fs/Traits.hxx"
|
||||||
#include "util/StringFormat.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
@ -71,7 +72,14 @@ UpdateWalk::UpdatePlaylistFile(Directory &parent, std::string_view name,
|
|||||||
|
|
||||||
auto db_song = std::make_unique<Song>(std::move(*song),
|
auto db_song = std::make_unique<Song>(std::move(*song),
|
||||||
*directory);
|
*directory);
|
||||||
db_song->target = "../" + db_song->filename;
|
db_song->target =
|
||||||
|
PathTraitsUTF8::IsAbsoluteOrHasScheme(db_song->filename.c_str())
|
||||||
|
? db_song->filename
|
||||||
|
/* prepend "../" to relative paths to
|
||||||
|
go from the virtual directory
|
||||||
|
(DEVICE_PLAYLIST) to the containing
|
||||||
|
directory */
|
||||||
|
: "../" + db_song->filename;
|
||||||
db_song->filename = StringFormat<64>("track%04u",
|
db_song->filename = StringFormat<64>("track%04u",
|
||||||
++track);
|
++track);
|
||||||
|
|
||||||
|
@ -133,6 +133,28 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UpdateWalk::PurgeDanglingFromPlaylists(Directory &directory) noexcept
|
||||||
|
{
|
||||||
|
/* recurse */
|
||||||
|
for (Directory &child : directory.children)
|
||||||
|
PurgeDanglingFromPlaylists(child);
|
||||||
|
|
||||||
|
if (!directory.IsPlaylist())
|
||||||
|
/* this check is only for virtual directories
|
||||||
|
representing a playlist file */
|
||||||
|
return;
|
||||||
|
|
||||||
|
directory.ForEachSongSafe([&](Song &song){
|
||||||
|
if (!song.target.empty() &&
|
||||||
|
!PathTraitsUTF8::IsAbsoluteOrHasScheme(song.target.c_str()) &&
|
||||||
|
!directory.TargetExists(song.target)) {
|
||||||
|
editor.DeleteSong(directory, &song);
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
static bool
|
static bool
|
||||||
update_directory_stat(Storage &storage, Directory &directory) noexcept
|
update_directory_stat(Storage &storage, Directory &directory) noexcept
|
||||||
@ -530,5 +552,10 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
|
|||||||
UpdateDirectory(root, exclude_list, info);
|
UpdateDirectory(root, exclude_list, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopeDatabaseLock protect;
|
||||||
|
PurgeDanglingFromPlaylists(root);
|
||||||
|
}
|
||||||
|
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,12 @@ private:
|
|||||||
|
|
||||||
void PurgeDeletedFromDirectory(Directory &directory) noexcept;
|
void PurgeDeletedFromDirectory(Directory &directory) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all virtual songs inside playlists whose "target"
|
||||||
|
* field points to a non-existing song file.
|
||||||
|
*/
|
||||||
|
void PurgeDanglingFromPlaylists(Directory &directory) noexcept;
|
||||||
|
|
||||||
void UpdateSongFile2(Directory &directory,
|
void UpdateSongFile2(Directory &directory,
|
||||||
const char *name, std::string_view suffix,
|
const char *name, std::string_view suffix,
|
||||||
const StorageFileInfo &info) noexcept;
|
const StorageFileInfo &info) noexcept;
|
||||||
|
@ -582,10 +582,6 @@ DecoderBridge::SubmitTag(InputStream *is, Tag &&tag) noexcept
|
|||||||
|
|
||||||
decoder_tag = std::make_unique<Tag>(std::move(tag));
|
decoder_tag = std::make_unique<Tag>(std::move(tag));
|
||||||
|
|
||||||
/* check for a new stream tag */
|
|
||||||
|
|
||||||
UpdateStreamTag(is);
|
|
||||||
|
|
||||||
/* check if we're seeking */
|
/* check if we're seeking */
|
||||||
|
|
||||||
if (PrepareInitialSeek())
|
if (PrepareInitialSeek())
|
||||||
@ -594,6 +590,10 @@ DecoderBridge::SubmitTag(InputStream *is, Tag &&tag) noexcept
|
|||||||
function here */
|
function here */
|
||||||
return DecoderCommand::SEEK;
|
return DecoderCommand::SEEK;
|
||||||
|
|
||||||
|
/* check for a new stream tag */
|
||||||
|
|
||||||
|
UpdateStreamTag(is);
|
||||||
|
|
||||||
/* send tag to music pipe */
|
/* send tag to music pipe */
|
||||||
|
|
||||||
if (stream_tag != nullptr)
|
if (stream_tag != nullptr)
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "Traits.hxx"
|
#include "Traits.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
|
#include "util/UriExtract.hxx"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -220,6 +221,12 @@ PathTraitsUTF8::Build(string_view a, string_view b) noexcept
|
|||||||
return BuildPathImpl<PathTraitsUTF8>(a, b);
|
return BuildPathImpl<PathTraitsUTF8>(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
PathTraitsUTF8::IsAbsoluteOrHasScheme(const_pointer p) noexcept
|
||||||
|
{
|
||||||
|
return IsAbsolute(p) || uri_has_scheme(p);
|
||||||
|
}
|
||||||
|
|
||||||
PathTraitsUTF8::const_pointer
|
PathTraitsUTF8::const_pointer
|
||||||
PathTraitsUTF8::GetBase(const_pointer p) noexcept
|
PathTraitsUTF8::GetBase(const_pointer p) noexcept
|
||||||
{
|
{
|
||||||
|
@ -274,6 +274,13 @@ struct PathTraitsUTF8 {
|
|||||||
return IsSeparator(*p);
|
return IsSeparator(*p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this any kind of absolute URI? (Unlike IsAbsolute(),
|
||||||
|
* this includes URIs/URLs with a scheme)
|
||||||
|
*/
|
||||||
|
[[gnu::pure]] [[gnu::nonnull]]
|
||||||
|
static bool IsAbsoluteOrHasScheme(const_pointer p) noexcept;
|
||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
static bool IsSpecialFilename(const_pointer name) noexcept {
|
static bool IsSpecialFilename(const_pointer name) noexcept {
|
||||||
return (name[0] == '.' && name[1] == 0) ||
|
return (name[0] == '.' && name[1] == 0) ||
|
||||||
|
@ -83,6 +83,24 @@ class AlsaMixer final : public Mixer {
|
|||||||
|
|
||||||
AlsaMixerMonitor *monitor;
|
AlsaMixerMonitor *monitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These fields are our workaround for rounding errors when
|
||||||
|
* the resolution of a mixer knob isn't fine enough to
|
||||||
|
* represent all 101 possible values (0..100).
|
||||||
|
*
|
||||||
|
* "desired_volume" is the percent value passed to
|
||||||
|
* SetVolume(), and "resulting_volume" is the volume which was
|
||||||
|
* actually set, and would be returned by the next
|
||||||
|
* GetPercentVolume() call.
|
||||||
|
*
|
||||||
|
* When GetVolume() is called, we compare the
|
||||||
|
* "resulting_volume" with the value returned by
|
||||||
|
* GetPercentVolume(), and if it's the same, we're still on
|
||||||
|
* the same value that was previously set (but may have been
|
||||||
|
* rounded down or up).
|
||||||
|
*/
|
||||||
|
int desired_volume, resulting_volume;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AlsaMixer(EventLoop &_event_loop, MixerListener &_listener)
|
AlsaMixer(EventLoop &_event_loop, MixerListener &_listener)
|
||||||
:Mixer(alsa_mixer_plugin, _listener),
|
:Mixer(alsa_mixer_plugin, _listener),
|
||||||
@ -101,6 +119,27 @@ public:
|
|||||||
void Close() noexcept override;
|
void Close() noexcept override;
|
||||||
int GetVolume() override;
|
int GetVolume() override;
|
||||||
void SetVolume(unsigned volume) override;
|
void SetVolume(unsigned volume) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[gnu::const]]
|
||||||
|
static unsigned NormalizedToPercent(double normalized) noexcept {
|
||||||
|
return lround(100 * normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[gnu::pure]]
|
||||||
|
double GetNormalizedVolume() const noexcept {
|
||||||
|
return get_normalized_playback_volume(elem,
|
||||||
|
SND_MIXER_SCHN_FRONT_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[gnu::pure]]
|
||||||
|
unsigned GetPercentVolume() const noexcept {
|
||||||
|
return NormalizedToPercent(GetNormalizedVolume());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ElemCallback(snd_mixer_elem_t *elem,
|
||||||
|
unsigned mask) noexcept;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr Domain alsa_mixer_domain("alsa_mixer");
|
static constexpr Domain alsa_mixer_domain("alsa_mixer");
|
||||||
@ -144,18 +183,26 @@ AlsaMixerMonitor::DispatchSockets() noexcept
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int
|
int
|
||||||
alsa_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned mask)
|
AlsaMixer::ElemCallback(snd_mixer_elem_t *elem, unsigned mask) noexcept
|
||||||
{
|
{
|
||||||
AlsaMixer &mixer = *(AlsaMixer *)
|
AlsaMixer &mixer = *(AlsaMixer *)
|
||||||
snd_mixer_elem_get_callback_private(elem);
|
snd_mixer_elem_get_callback_private(elem);
|
||||||
|
|
||||||
if (mask & SND_CTL_EVENT_MASK_VALUE) {
|
if (mask & SND_CTL_EVENT_MASK_VALUE) {
|
||||||
try {
|
int volume = mixer.GetPercentVolume();
|
||||||
int volume = mixer.GetVolume();
|
|
||||||
mixer.listener.OnMixerVolumeChanged(mixer, volume);
|
if (mixer.resulting_volume >= 0 &&
|
||||||
} catch (...) {
|
volume == mixer.resulting_volume)
|
||||||
}
|
/* still the same volume (this might be a
|
||||||
|
callback caused by SetVolume()) - switch to
|
||||||
|
desired_volume */
|
||||||
|
volume = mixer.desired_volume;
|
||||||
|
else
|
||||||
|
/* flush */
|
||||||
|
mixer.desired_volume = mixer.resulting_volume = -1;
|
||||||
|
|
||||||
|
mixer.listener.OnMixerVolumeChanged(mixer, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -233,7 +280,7 @@ AlsaMixer::Setup()
|
|||||||
throw FormatRuntimeError("no such mixer control: %s", control);
|
throw FormatRuntimeError("no such mixer control: %s", control);
|
||||||
|
|
||||||
snd_mixer_elem_set_callback_private(elem, this);
|
snd_mixer_elem_set_callback_private(elem, this);
|
||||||
snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback);
|
snd_mixer_elem_set_callback(elem, ElemCallback);
|
||||||
|
|
||||||
monitor = new AlsaMixerMonitor(event_loop, handle);
|
monitor = new AlsaMixerMonitor(event_loop, handle);
|
||||||
}
|
}
|
||||||
@ -241,6 +288,8 @@ AlsaMixer::Setup()
|
|||||||
void
|
void
|
||||||
AlsaMixer::Open()
|
AlsaMixer::Open()
|
||||||
{
|
{
|
||||||
|
desired_volume = resulting_volume = -1;
|
||||||
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_mixer_open(&handle, 0);
|
err = snd_mixer_open(&handle, 0);
|
||||||
@ -279,7 +328,12 @@ AlsaMixer::GetVolume()
|
|||||||
throw FormatRuntimeError("snd_mixer_handle_events() failed: %s",
|
throw FormatRuntimeError("snd_mixer_handle_events() failed: %s",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
return lround(100 * get_normalized_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT));
|
int volume = GetPercentVolume();
|
||||||
|
if (resulting_volume >= 0 && volume == resulting_volume)
|
||||||
|
/* we're still on the value passed to SetVolume() */
|
||||||
|
volume = desired_volume;
|
||||||
|
|
||||||
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -287,12 +341,13 @@ AlsaMixer::SetVolume(unsigned volume)
|
|||||||
{
|
{
|
||||||
assert(handle != nullptr);
|
assert(handle != nullptr);
|
||||||
|
|
||||||
double cur = get_normalized_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT);
|
int err = set_normalized_playback_volume(elem, 0.01*volume, 1);
|
||||||
int delta = volume - lround(100.*cur);
|
|
||||||
int err = set_normalized_playback_volume(elem, cur + 0.01*delta, delta);
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("failed to set ALSA volume: %s",
|
throw FormatRuntimeError("failed to set ALSA volume: %s",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
|
desired_volume = volume;
|
||||||
|
resulting_volume = GetPercentVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
const MixerPlugin alsa_mixer_plugin = {
|
const MixerPlugin alsa_mixer_plugin = {
|
||||||
|
@ -95,8 +95,8 @@ playlist_check_translate_song(DetachedSong &song, std::string_view base_uri,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (base_uri.data() != nullptr && !uri_has_scheme(uri) &&
|
if (base_uri.data() != nullptr &&
|
||||||
!PathTraitsUTF8::IsAbsolute(uri))
|
!PathTraitsUTF8::IsAbsoluteOrHasScheme(uri))
|
||||||
song.SetURI(PathTraitsUTF8::Build(base_uri, uri));
|
song.SetURI(PathTraitsUTF8::Build(base_uri, uri));
|
||||||
|
|
||||||
return playlist_check_load_song(song, loader);
|
return playlist_check_load_song(song, loader);
|
||||||
|
@ -61,7 +61,7 @@ DetachedSong::IsInDatabase() const noexcept
|
|||||||
GetRealURI() is never relative */
|
GetRealURI() is never relative */
|
||||||
|
|
||||||
const char *_uri = GetURI();
|
const char *_uri = GetURI();
|
||||||
return !uri_has_scheme(_uri) && !PathTraitsUTF8::IsAbsolute(_uri);
|
return !PathTraitsUTF8::IsAbsoluteOrHasScheme(_uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
SignedSongTime
|
SignedSongTime
|
||||||
|
Loading…
Reference in New Issue
Block a user