Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fff25ac753 | ||
![]() |
4f1e79b6b8 | ||
![]() |
aa9933c0b5 | ||
![]() |
0697d1f859 | ||
![]() |
df033fa4aa | ||
![]() |
b941a7df83 | ||
![]() |
31151cec3c | ||
![]() |
07e8c338df | ||
![]() |
b22d7218aa | ||
![]() |
d5be8c74b0 | ||
![]() |
c112cb60da | ||
![]() |
677fa4f9bc | ||
![]() |
907af2ad02 | ||
![]() |
6a2e7bbc02 | ||
![]() |
771c46032f | ||
![]() |
85611aa456 | ||
![]() |
466b5cb08d | ||
![]() |
3f2f3251cb | ||
![]() |
8ae85f3991 | ||
![]() |
781fe4ff28 |
NEWS
android
doc
meson.buildsrc
Permission.cxxPermission.hxxSongPrint.cxxTagPrint.cxx
command
ClientCommands.cxxPlayerCommands.cxxPlaylistCommands.cxxPositionArg.cxxPositionArg.hxxQueueCommands.cxx
filter
plugins
lib
upnp
mixer
plugins
neighbor
output
plugins
pcm
playlist
plugins
protocol
storage
zeroconf
avahi
test
12
NEWS
12
NEWS
@@ -1,3 +1,15 @@
|
||||
ver 0.23.1 (2021/10/19)
|
||||
* protocol
|
||||
- use decimal notation instead of scientific notation
|
||||
- "load" supports relative positions
|
||||
* output
|
||||
- emit "mixer" idle event when replay gain changes volume
|
||||
- pipewire: emit "mixer" idle events on external volume change
|
||||
- pipewire: attempt to change the graph sample rate
|
||||
- snapcast: fix time stamp bug which caused "Failed to get chunk"
|
||||
* fix libfmt linker problems
|
||||
* fix broken password authentication
|
||||
|
||||
ver 0.23 (2021/10/14)
|
||||
* protocol
|
||||
- new command "getvol"
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="60"
|
||||
android:versionName="0.23">
|
||||
android:versionCode="61"
|
||||
android:versionName="0.23.1">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
|
||||
|
@@ -38,9 +38,9 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.23'
|
||||
version = '0.23.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version + '~git'
|
||||
#release = version + '~git'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@@ -1193,6 +1193,8 @@ allows MPD to act as a `Snapcast
|
||||
<https://github.com/badaix/snapcast>`__ server. Snapcast clients
|
||||
connect to it and receive audio data from MPD.
|
||||
|
||||
You must set a format.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
@@ -923,8 +923,9 @@ remote playlists (absolute URI with a supported scheme).
|
||||
only a part of the playlist.
|
||||
|
||||
The ``POSITION`` parameter specifies where the songs will be
|
||||
inserted into the queue. (This requires specifying the range as
|
||||
well; the special value `0:` can be used if the whole playlist
|
||||
inserted into the queue; it can be relative as described in
|
||||
:ref:`addid <command_addid>`. (This requires specifying the range
|
||||
as well; the special value `0:` can be used if the whole playlist
|
||||
shall be loaded at a certain queue position.)
|
||||
|
||||
.. _command_playlistadd:
|
||||
@@ -1059,11 +1060,11 @@ The music database
|
||||
|
||||
.. _command_findadd:
|
||||
|
||||
:command:`findadd {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
:command:`findadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
||||
the queue. Parameters have the same meaning as for
|
||||
:ref:`find <command_find>`.
|
||||
:ref:`find <command_find>` and :ref:`searchadd <command_searchadd>`.
|
||||
|
||||
.. _command_list:
|
||||
|
||||
@@ -1196,15 +1197,12 @@ The music database
|
||||
|
||||
.. _command_search:
|
||||
|
||||
:command:`search {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||
:command:`search {FILTER} [sort {TYPE}] [window {START:END}]
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`). Parameters
|
||||
have the same meaning as for :ref:`find <command_find>`,
|
||||
except that search is not case sensitive.
|
||||
|
||||
The ``position`` parameter specifies where the songs will be
|
||||
inserted.
|
||||
|
||||
.. _command_searchadd:
|
||||
|
||||
:command:`searchadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||
@@ -1214,6 +1212,9 @@ The music database
|
||||
|
||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||
|
||||
The ``position`` parameter specifies where the songs will be
|
||||
inserted.
|
||||
|
||||
.. _command_searchaddpl:
|
||||
|
||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
|
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.23',
|
||||
version: '0.23.1',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@@ -44,7 +44,7 @@ version_conf = configuration_data()
|
||||
version_conf.set_quoted('PACKAGE', meson.project_name())
|
||||
version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
version_conf.set_quoted('VERSION', meson.project_version())
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.0')
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.1')
|
||||
configure_file(output: 'Version.h', configuration: version_conf)
|
||||
|
||||
conf = configuration_data()
|
||||
@@ -267,6 +267,7 @@ sources = [
|
||||
'src/protocol/ArgParser.cxx',
|
||||
'src/protocol/Result.cxx',
|
||||
'src/command/CommandError.cxx',
|
||||
'src/command/PositionArg.cxx',
|
||||
'src/command/AllCommands.cxx',
|
||||
'src/command/QueueCommands.cxx',
|
||||
'src/command/TagCommands.cxx',
|
||||
|
@@ -161,15 +161,14 @@ GetPermissionsFromAddress(SocketAddress address) noexcept
|
||||
|
||||
#endif
|
||||
|
||||
int
|
||||
getPermissionFromPassword(const char *password, unsigned *permission) noexcept
|
||||
std::optional<unsigned>
|
||||
GetPermissionFromPassword(const char *password) noexcept
|
||||
{
|
||||
auto i = permission_passwords.find(password);
|
||||
if (i == permission_passwords.end())
|
||||
return -1;
|
||||
return std::nullopt;
|
||||
|
||||
*permission = i->second;
|
||||
return 0;
|
||||
return i->second;
|
||||
}
|
||||
|
||||
unsigned
|
||||
|
@@ -22,6 +22,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
struct ConfigData;
|
||||
class SocketAddress;
|
||||
|
||||
@@ -32,9 +34,13 @@ static constexpr unsigned PERMISSION_CONTROL = 4;
|
||||
static constexpr unsigned PERMISSION_ADMIN = 8;
|
||||
static constexpr unsigned PERMISSION_PLAYER = 16;
|
||||
|
||||
/**
|
||||
* @return the permissions for the given password or std::nullopt if
|
||||
* the password is not accepted
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
int
|
||||
getPermissionFromPassword(const char *password, unsigned *permission) noexcept;
|
||||
std::optional<unsigned>
|
||||
GetPermissionFromPassword(const char *password) noexcept;
|
||||
|
||||
[[gnu::const]]
|
||||
unsigned
|
||||
|
@@ -100,7 +100,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
|
||||
const auto duration = song.GetDuration();
|
||||
if (!duration.IsNegative())
|
||||
r.Fmt(FMT_STRING("Time: {}\n"
|
||||
"duration: {:1.3}\n"),
|
||||
"duration: {:1.3f}\n"),
|
||||
duration.RoundS(),
|
||||
duration.ToDoubleS());
|
||||
}
|
||||
@@ -123,7 +123,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
|
||||
const auto duration = song.GetDuration();
|
||||
if (!duration.IsNegative())
|
||||
r.Fmt(FMT_STRING("Time: {}\n"
|
||||
"duration: {:1.3}\n"),
|
||||
"duration: {:1.3f}\n"),
|
||||
duration.RoundS(),
|
||||
duration.ToDoubleS());
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ tag_print(Response &r, const Tag &tag) noexcept
|
||||
{
|
||||
if (!tag.duration.IsNegative())
|
||||
r.Fmt(FMT_STRING("Time: {}\n"
|
||||
"duration: {:1.3}\n"),
|
||||
"duration: {:1.3f}\n"),
|
||||
tag.duration.RoundS(),
|
||||
tag.duration.ToDoubleS());
|
||||
|
||||
|
@@ -58,13 +58,13 @@ handle_binary_limit(Client &client, Request args,
|
||||
CommandResult
|
||||
handle_password(Client &client, Request args, Response &r)
|
||||
{
|
||||
unsigned permission = 0;
|
||||
if (getPermissionFromPassword(args.front(), &permission) < 0) {
|
||||
const auto permission = GetPermissionFromPassword(args.front());
|
||||
if (!permission) {
|
||||
r.Error(ACK_ERROR_PASSWORD, "incorrect password");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
client.SetPermission(permission);
|
||||
client.SetPermission(*permission);
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
@@ -171,7 +171,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
||||
|
||||
if (player_status.state != PlayerState::STOP) {
|
||||
r.Fmt(FMT_STRING(COMMAND_STATUS_TIME ": {}:{}\n"
|
||||
"elapsed: {:1.3}\n"
|
||||
"elapsed: {:1.3f}\n"
|
||||
COMMAND_STATUS_BITRATE ": {}\n"),
|
||||
player_status.elapsed_time.RoundS(),
|
||||
player_status.total_time.IsNegative()
|
||||
@@ -181,7 +181,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
||||
player_status.bit_rate);
|
||||
|
||||
if (!player_status.total_time.IsNegative())
|
||||
r.Fmt(FMT_STRING("duration: {:1.3}\n"),
|
||||
r.Fmt(FMT_STRING("duration: {:1.3f}\n"),
|
||||
player_status.total_time.ToDoubleS());
|
||||
|
||||
if (player_status.audio_format.IsDefined())
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "PlaylistCommands.hxx"
|
||||
#include "PositionArg.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "db/Selection.hxx"
|
||||
@@ -86,7 +87,7 @@ handle_load(Client &client, Request args, [[maybe_unused]] Response &r)
|
||||
const unsigned old_size = playlist.GetLength();
|
||||
|
||||
const unsigned position = args.size > 2
|
||||
? args.ParseUnsigned(2, old_size)
|
||||
? ParseInsertPosition(args[2], partition.playlist)
|
||||
: old_size;
|
||||
|
||||
const SongLoader loader(client);
|
||||
|
103
src/command/PositionArg.cxx
Normal file
103
src/command/PositionArg.cxx
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "PositionArg.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
#include "protocol/ArgParser.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
#include "queue/Playlist.hxx"
|
||||
|
||||
static unsigned
|
||||
RequireCurrentPosition(const playlist &p)
|
||||
{
|
||||
int position = p.GetCurrentPosition();
|
||||
if (position < 0)
|
||||
throw ProtocolError(ACK_ERROR_PLAYER_SYNC,
|
||||
"No current song");
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
unsigned
|
||||
ParseInsertPosition(const char *s, const playlist &playlist)
|
||||
{
|
||||
const auto queue_length = playlist.queue.GetLength();
|
||||
|
||||
if (*s == '+') {
|
||||
/* after the current song */
|
||||
|
||||
const unsigned current = RequireCurrentPosition(playlist);
|
||||
assert(current < queue_length);
|
||||
|
||||
return current + 1 +
|
||||
ParseCommandArgUnsigned(s + 1,
|
||||
queue_length - current - 1);
|
||||
} else if (*s == '-') {
|
||||
/* before the current song */
|
||||
|
||||
const unsigned current = RequireCurrentPosition(playlist);
|
||||
assert(current < queue_length);
|
||||
|
||||
return current - ParseCommandArgUnsigned(s + 1, current);
|
||||
} else
|
||||
/* absolute position */
|
||||
return ParseCommandArgUnsigned(s, queue_length);
|
||||
}
|
||||
|
||||
unsigned
|
||||
ParseMoveDestination(const char *s, const RangeArg range, const playlist &p)
|
||||
{
|
||||
assert(!range.IsEmpty());
|
||||
assert(!range.IsOpenEnded());
|
||||
|
||||
const unsigned queue_length = p.queue.GetLength();
|
||||
|
||||
if (*s == '+') {
|
||||
/* after the current song */
|
||||
|
||||
unsigned current = RequireCurrentPosition(p);
|
||||
assert(current < queue_length);
|
||||
if (range.Contains(current))
|
||||
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
||||
|
||||
if (current >= range.end)
|
||||
current -= range.Count();
|
||||
|
||||
return current + 1 +
|
||||
ParseCommandArgUnsigned(s + 1,
|
||||
queue_length - current - range.Count());
|
||||
} else if (*s == '-') {
|
||||
/* before the current song */
|
||||
|
||||
unsigned current = RequireCurrentPosition(p);
|
||||
assert(current < queue_length);
|
||||
if (range.Contains(current))
|
||||
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
||||
|
||||
if (current >= range.end)
|
||||
current -= range.Count();
|
||||
|
||||
return current -
|
||||
ParseCommandArgUnsigned(s + 1,
|
||||
queue_length - current - range.Count());
|
||||
} else
|
||||
/* absolute position */
|
||||
return ParseCommandArgUnsigned(s,
|
||||
queue_length - range.Count());
|
||||
}
|
32
src/command/PositionArg.hxx
Normal file
32
src/command/PositionArg.hxx
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
struct playlist;
|
||||
struct RangeArg;
|
||||
|
||||
/**
|
||||
* Throws #ProtocolError on error.
|
||||
*/
|
||||
unsigned
|
||||
ParseInsertPosition(const char *s, const playlist &playlist);
|
||||
|
||||
unsigned
|
||||
ParseMoveDestination(const char *s, const RangeArg range, const playlist &p);
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "QueueCommands.hxx"
|
||||
#include "PositionArg.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
#include "db/DatabaseQueue.hxx"
|
||||
@@ -43,17 +44,6 @@
|
||||
|
||||
#include <limits>
|
||||
|
||||
static unsigned
|
||||
RequireCurrentPosition(const playlist &p)
|
||||
{
|
||||
int position = p.GetCurrentPosition();
|
||||
if (position < 0)
|
||||
throw ProtocolError(ACK_ERROR_PLAYER_SYNC,
|
||||
"No current song");
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
static void
|
||||
AddUri(Client &client, const LocatedUri &uri)
|
||||
{
|
||||
@@ -129,30 +119,8 @@ handle_addid(Client &client, Request args, Response &r)
|
||||
|
||||
const auto queue_length = partition.playlist.queue.GetLength();
|
||||
|
||||
if (args.size > 1) {
|
||||
const char *const s = args[1];
|
||||
if (*s == '+') {
|
||||
/* after the current song */
|
||||
|
||||
const unsigned current =
|
||||
RequireCurrentPosition(partition.playlist);
|
||||
assert(current < queue_length);
|
||||
|
||||
to = current + 1 +
|
||||
ParseCommandArgUnsigned(s + 1,
|
||||
queue_length - current - 1);
|
||||
} else if (*s == '-') {
|
||||
/* before the current song */
|
||||
|
||||
const unsigned current =
|
||||
RequireCurrentPosition(partition.playlist);
|
||||
assert(current < queue_length);
|
||||
|
||||
to = current - ParseCommandArgUnsigned(s + 1, current);
|
||||
} else
|
||||
/* absolute position */
|
||||
to = args.ParseUnsigned(1, queue_length);
|
||||
}
|
||||
if (args.size > 1)
|
||||
to = ParseInsertPosition(args[1], partition.playlist);
|
||||
|
||||
const SongLoader loader(client);
|
||||
const unsigned added_position = queue_length;
|
||||
@@ -363,49 +331,6 @@ handle_prioid(Client &client, Request args, [[maybe_unused]] Response &r)
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
ParseMoveDestination(const char *s, const RangeArg range,
|
||||
const playlist &p)
|
||||
{
|
||||
assert(!range.IsEmpty());
|
||||
assert(!range.IsOpenEnded());
|
||||
|
||||
const unsigned queue_length = p.queue.GetLength();
|
||||
|
||||
if (*s == '+') {
|
||||
/* after the current song */
|
||||
|
||||
unsigned current = RequireCurrentPosition(p);
|
||||
assert(current < queue_length);
|
||||
if (range.Contains(current))
|
||||
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
||||
|
||||
if (current >= range.end)
|
||||
current -= range.Count();
|
||||
|
||||
return current + 1 +
|
||||
ParseCommandArgUnsigned(s + 1,
|
||||
queue_length - current - range.Count());
|
||||
} else if (*s == '-') {
|
||||
/* before the current song */
|
||||
|
||||
unsigned current = RequireCurrentPosition(p);
|
||||
assert(current < queue_length);
|
||||
if (range.Contains(current))
|
||||
throw ProtocolError(ACK_ERROR_ARG, "Cannot move current song relative to itself");
|
||||
|
||||
if (current >= range.end)
|
||||
current -= range.Count();
|
||||
|
||||
return current -
|
||||
ParseCommandArgUnsigned(s + 1,
|
||||
queue_length - current - range.Count());
|
||||
} else
|
||||
/* absolute position */
|
||||
return ParseCommandArgUnsigned(s,
|
||||
queue_length - range.Count());
|
||||
}
|
||||
|
||||
static CommandResult
|
||||
handle_move(Partition &partition, RangeArg range, const char *to)
|
||||
{
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <cassert>
|
||||
@@ -169,6 +170,10 @@ ReplayGainFilter::Update()
|
||||
|
||||
try {
|
||||
mixer_set_volume(mixer, _volume);
|
||||
|
||||
/* TODO: emit this idle event only for the
|
||||
current partition */
|
||||
idle_add(IDLE_MIXER);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception(),
|
||||
"Failed to update hardware mixer");
|
||||
|
@@ -51,6 +51,7 @@ upnp = static_library(
|
||||
'Util.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
log_dep,
|
||||
upnp_dep,
|
||||
curl_dep,
|
||||
expat_dep,
|
||||
|
@@ -37,6 +37,8 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
~PipeWireMixer() noexcept override;
|
||||
|
||||
PipeWireMixer(const PipeWireMixer &) = delete;
|
||||
PipeWireMixer &operator=(const PipeWireMixer &) = delete;
|
||||
|
||||
@@ -82,7 +84,14 @@ pipewire_mixer_init([[maybe_unused]] EventLoop &event_loop, AudioOutput &ao,
|
||||
const ConfigBlock &)
|
||||
{
|
||||
auto &po = (PipeWireOutput &)ao;
|
||||
return new PipeWireMixer(po, listener);
|
||||
auto *pm = new PipeWireMixer(po, listener);
|
||||
pipewire_output_set_mixer(po, *pm);
|
||||
return pm;
|
||||
}
|
||||
|
||||
PipeWireMixer::~PipeWireMixer() noexcept
|
||||
{
|
||||
pipewire_output_clear_mixer(output, *this);
|
||||
}
|
||||
|
||||
const MixerPlugin pipewire_mixer_plugin = {
|
||||
|
@@ -33,6 +33,8 @@
|
||||
|
||||
#include <libsmbclient.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
class SmbclientNeighborExplorer final : public NeighborExplorer {
|
||||
@@ -45,12 +47,12 @@ class SmbclientNeighborExplorer final : public NeighborExplorer {
|
||||
|
||||
Server(const Server &) = delete;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool operator==(const Server &other) const noexcept {
|
||||
return name == other.name;
|
||||
}
|
||||
|
||||
[[nodiscard]] gcc_pure
|
||||
[[nodiscard]] [[gnu::pure]]
|
||||
NeighborInfo Export() const noexcept {
|
||||
return { "smb://" + name + "/", comment };
|
||||
}
|
||||
@@ -165,11 +167,11 @@ ReadServers(SmbclientContext &ctx, const char *uri,
|
||||
ReadServers(ctx, handle, list);
|
||||
ctx.CloseDirectory(handle);
|
||||
} else
|
||||
FormatErrno(smbclient_domain, "smbc_opendir('%s') failed",
|
||||
uri);
|
||||
FmtError(smbclient_domain, "smbc_opendir('{}') failed: {}",
|
||||
uri, strerror(errno));
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
static NeighborExplorer::List
|
||||
DetectServers(SmbclientContext &ctx) noexcept
|
||||
{
|
||||
@@ -178,7 +180,7 @@ DetectServers(SmbclientContext &ctx) noexcept
|
||||
return list;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
static NeighborExplorer::List::iterator
|
||||
FindBeforeServerByURI(NeighborExplorer::List::iterator prev,
|
||||
NeighborExplorer::List::iterator end,
|
||||
|
@@ -25,6 +25,7 @@ neighbor_plugins = static_library(
|
||||
neighbor_plugins_sources,
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
log_dep,
|
||||
dbus_dep,
|
||||
smbclient_dep,
|
||||
upnp_dep,
|
||||
|
@@ -41,6 +41,8 @@
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
@@ -75,6 +77,9 @@ class PipeWireOutput final : AudioOutput {
|
||||
|
||||
float volume = 1.0;
|
||||
|
||||
PipeWireMixer *mixer = nullptr;
|
||||
int channels;
|
||||
|
||||
/**
|
||||
* The active sample format, needed for PcmSilence().
|
||||
*/
|
||||
@@ -124,11 +129,21 @@ public:
|
||||
events.state_changed = StateChanged;
|
||||
events.process = Process;
|
||||
events.drained = Drained;
|
||||
events.control_info = ControlInfo;
|
||||
events.param_changed = ParamChanged;
|
||||
return events;
|
||||
}
|
||||
|
||||
void SetVolume(float volume);
|
||||
|
||||
void SetMixer(PipeWireMixer &_mixer) noexcept;
|
||||
|
||||
void ClearMixer([[maybe_unused]] PipeWireMixer &old_mixer) noexcept {
|
||||
assert(mixer == &old_mixer);
|
||||
|
||||
mixer = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void CheckThrowError() {
|
||||
if (disconnected)
|
||||
@@ -163,6 +178,46 @@ private:
|
||||
o.Drained();
|
||||
}
|
||||
|
||||
void ControlInfo(const struct pw_stream_control *control) noexcept {
|
||||
float sum = 0;
|
||||
unsigned c;
|
||||
for (c = 0; c < control->n_values; c++)
|
||||
sum += control->values[c];
|
||||
|
||||
sum /= control->n_values;
|
||||
|
||||
if (mixer != nullptr)
|
||||
pipewire_mixer_on_change(*mixer, std::cbrt(sum));
|
||||
|
||||
pw_thread_loop_signal(thread_loop, false);
|
||||
}
|
||||
|
||||
static void ControlInfo(void *data,
|
||||
[[maybe_unused]] uint32_t id,
|
||||
const struct pw_stream_control *control) noexcept {
|
||||
auto &o = *(PipeWireOutput *)data;
|
||||
if (StringIsEqual(control->name, "Channel Volumes"))
|
||||
o.ControlInfo(control);
|
||||
}
|
||||
|
||||
void ParamChanged() noexcept {
|
||||
if (restore_volume) {
|
||||
SetVolume(volume);
|
||||
restore_volume = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void ParamChanged(void *data,
|
||||
uint32_t id,
|
||||
const struct spa_pod *param) noexcept
|
||||
{
|
||||
if (id != SPA_PARAM_Format || param == NULL)
|
||||
return;
|
||||
|
||||
auto &o = *(PipeWireOutput *)data;
|
||||
o.ParamChanged();
|
||||
}
|
||||
|
||||
/* virtual methods from class AudioOutput */
|
||||
void Enable() override;
|
||||
void Disable() noexcept override;
|
||||
@@ -214,11 +269,20 @@ PipeWireOutput::SetVolume(float _volume)
|
||||
{
|
||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||
|
||||
if (stream != nullptr && !restore_volume &&
|
||||
pw_stream_set_control(stream,
|
||||
SPA_PROP_volume, 1, &_volume,
|
||||
float newvol = _volume*_volume*_volume;
|
||||
|
||||
if (stream != nullptr && !restore_volume) {
|
||||
float vol[SPA_AUDIO_MAX_CHANNELS];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
vol[i] = newvol;
|
||||
}
|
||||
if (pw_stream_set_control(stream,
|
||||
SPA_PROP_channelVolumes, channels, vol,
|
||||
0) != 0)
|
||||
throw std::runtime_error("pw_stream_set_control() failed");
|
||||
throw std::runtime_error("pw_stream_set_control() failed");
|
||||
}
|
||||
|
||||
volume = _volume;
|
||||
}
|
||||
@@ -383,6 +447,13 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
||||
if (target != nullptr && target_id == PW_ID_ANY)
|
||||
pw_properties_setf(props, PW_KEY_NODE_TARGET, "%s", target);
|
||||
|
||||
#ifdef PW_KEY_NODE_RATE
|
||||
/* ask PipeWire to change the graph sample rate to ours
|
||||
(requires PipeWire 0.3.32) */
|
||||
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u",
|
||||
audio_format.sample_rate);
|
||||
#endif
|
||||
|
||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||
|
||||
stream = pw_stream_new_simple(pw_thread_loop_get_loop(thread_loop),
|
||||
@@ -397,6 +468,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
||||
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
sample_format = audio_format.format;
|
||||
channels = audio_format.channels;
|
||||
interrupted = false;
|
||||
|
||||
/* allocate a ring buffer of 0.5 seconds */
|
||||
@@ -444,14 +516,6 @@ PipeWireOutput::StateChanged(enum pw_stream_state state,
|
||||
if (!was_disconnected && disconnected)
|
||||
pw_thread_loop_signal(thread_loop, false);
|
||||
|
||||
if (state == PW_STREAM_STATE_STREAMING && restore_volume) {
|
||||
/* restore the last known volume after creating a new
|
||||
pw_stream */
|
||||
restore_volume = false;
|
||||
pw_stream_set_control(stream,
|
||||
SPA_PROP_volume, 1, &volume,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -578,6 +642,28 @@ PipeWireOutput::Pause() noexcept
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void
|
||||
PipeWireOutput::SetMixer(PipeWireMixer &_mixer) noexcept
|
||||
{
|
||||
assert(mixer == nullptr);
|
||||
|
||||
mixer = &_mixer;
|
||||
|
||||
// TODO: Check if context and stream is ready and trigger a volume update...
|
||||
}
|
||||
|
||||
void
|
||||
pipewire_output_set_mixer(PipeWireOutput &po, PipeWireMixer &pm) noexcept
|
||||
{
|
||||
po.SetMixer(pm);
|
||||
}
|
||||
|
||||
void
|
||||
pipewire_output_clear_mixer(PipeWireOutput &po, PipeWireMixer &pm) noexcept
|
||||
{
|
||||
po.ClearMixer(pm);
|
||||
}
|
||||
|
||||
const struct AudioOutputPlugin pipewire_output_plugin = {
|
||||
"pipewire",
|
||||
nullptr,
|
||||
|
@@ -21,9 +21,16 @@
|
||||
#define MPD_PIPEWIRE_OUTPUT_PLUGIN_HXX
|
||||
|
||||
class PipeWireOutput;
|
||||
class PipeWireMixer;
|
||||
|
||||
extern const struct AudioOutputPlugin pipewire_output_plugin;
|
||||
|
||||
void
|
||||
pipewire_output_set_mixer(PipeWireOutput &po, PipeWireMixer &pm) noexcept;
|
||||
|
||||
void
|
||||
pipewire_output_clear_mixer(PipeWireOutput &po, PipeWireMixer &pm) noexcept;
|
||||
|
||||
void
|
||||
pipewire_output_set_volume(PipeWireOutput &output, float volume);
|
||||
|
||||
|
@@ -45,7 +45,7 @@ struct SnapcastTimestamp {
|
||||
|
||||
if (a_usec < b_usec) {
|
||||
--result_sec;
|
||||
result_usec += 1'000'0000;
|
||||
result_usec += 1'000'000;
|
||||
}
|
||||
|
||||
return {result_sec, result_usec};
|
||||
|
@@ -228,22 +228,22 @@ SoxrPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate)
|
||||
FmtDebug(soxr_domain, "soxr engine '{}'", soxr_engine(soxr));
|
||||
if (soxr_use_custom_recipe)
|
||||
FmtDebug(soxr_domain,
|
||||
"soxr precision={:0.0}, phase_response={:0.2}, "
|
||||
"passband_end={:0.2}, stopband_begin={:0.2} scale={:0.2}",
|
||||
"soxr precision={:0.0f}, phase_response={:0.2f}, "
|
||||
"passband_end={:0.2f}, stopband_begin={:0.2f} scale={:0.2f}",
|
||||
soxr_quality.precision, soxr_quality.phase_response,
|
||||
soxr_quality.passband_end, soxr_quality.stopband_begin,
|
||||
soxr_io_custom_recipe.scale);
|
||||
else
|
||||
FmtDebug(soxr_domain,
|
||||
"soxr precision={:0.0}, phase_response={:0.2}, "
|
||||
"passband_end={:0.2}, stopband_begin={:0.2}",
|
||||
"soxr precision={:0.0f}, phase_response={:0.2f}, "
|
||||
"passband_end={:0.2f}, stopband_begin={:0.2f}",
|
||||
soxr_quality.precision, soxr_quality.phase_response,
|
||||
soxr_quality.passband_end, soxr_quality.stopband_begin);
|
||||
|
||||
channels = af.channels;
|
||||
|
||||
ratio = float(new_sample_rate) / float(af.sample_rate);
|
||||
FmtDebug(soxr_domain, "samplerate conversion ratio to {:.2}", ratio);
|
||||
FmtDebug(soxr_domain, "samplerate conversion ratio to {:0.2f}", ratio);
|
||||
|
||||
/* libsoxr works with floating point samples */
|
||||
af.format = SampleFormat::FLOAT;
|
||||
|
@@ -5,6 +5,7 @@ playlist_plugins_sources = [
|
||||
]
|
||||
|
||||
playlist_plugins_deps = [
|
||||
log_dep,
|
||||
expat_dep,
|
||||
flac_dep,
|
||||
]
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include "Chrono.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static inline ProtocolError
|
||||
|
@@ -186,15 +186,15 @@ SmbclientDirectoryReader::GetInfo([[maybe_unused]] bool follow)
|
||||
static std::unique_ptr<Storage>
|
||||
CreateSmbclientStorageURI([[maybe_unused]] EventLoop &event_loop, const char *base)
|
||||
{
|
||||
if (!StringStartsWithCaseASCII(base, "smb://"))
|
||||
return nullptr;
|
||||
|
||||
SmbclientInit();
|
||||
|
||||
return std::make_unique<SmbclientStorage>(base);
|
||||
}
|
||||
|
||||
static constexpr const char *smbclient_prefixes[] = { "smb://", nullptr };
|
||||
|
||||
const StoragePlugin smbclient_storage_plugin = {
|
||||
"smbclient",
|
||||
smbclient_prefixes,
|
||||
CreateSmbclientStorageURI,
|
||||
};
|
||||
|
@@ -44,6 +44,7 @@ storage_plugins = static_library(
|
||||
storage_plugins_sources,
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
log_dep,
|
||||
curl_dep,
|
||||
dbus_dep,
|
||||
expat_dep,
|
||||
|
@@ -13,6 +13,7 @@ avahi = static_library(
|
||||
'Publisher.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
log_dep,
|
||||
libavahi_client,
|
||||
],
|
||||
)
|
||||
|
@@ -127,6 +127,7 @@ if enable_inotify
|
||||
'../src/db/update/InotifySource.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
log_dep,
|
||||
event_dep,
|
||||
util_dep,
|
||||
],
|
||||
|
Reference in New Issue
Block a user