Implemented volume control for jukebox
This commit is contained in:
@@ -238,6 +238,15 @@ namespace lms::audio::alsa
|
||||
return ::snd_pcm_state(_pcm.get()) == SND_PCM_STATE_PAUSED;
|
||||
}
|
||||
|
||||
void AudioOutputStream::setVolume(float)
|
||||
{
|
||||
}
|
||||
|
||||
float AudioOutputStream::getVolume() const
|
||||
{
|
||||
return 1.F;
|
||||
}
|
||||
|
||||
std::chrono::microseconds AudioOutputStream::getPlaybackTime() const
|
||||
{
|
||||
::snd_pcm_sframes_t delayFrames{};
|
||||
|
||||
@@ -52,15 +52,18 @@ namespace lms::audio::alsa
|
||||
void asyncWrite(std::span<const std::byte> buffer, WriteCompletionCallback cb) override;
|
||||
void asyncDrain(DrainCompletionCallback cb) override;
|
||||
|
||||
void pause() override;
|
||||
void resume() override;
|
||||
bool isPaused() const override;
|
||||
|
||||
std::chrono::microseconds getPlaybackTime() const override;
|
||||
std::chrono::microseconds getLatency() const override;
|
||||
|
||||
void flush() override;
|
||||
|
||||
void pause() override;
|
||||
void resume() override;
|
||||
bool isPaused() const override;
|
||||
|
||||
void setVolume(float volume) override;
|
||||
float getVolume() const override;
|
||||
|
||||
void stop();
|
||||
void setupAllDescriptors();
|
||||
void releaseAllDescriptors();
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
|
||||
#include "AudioOutputStream.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <pulse/context.h>
|
||||
#include <pulse/def.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/introspect.h>
|
||||
#include <pulse/operation.h>
|
||||
#include <pulse/proplist.h>
|
||||
#include <pulse/stream.h>
|
||||
@@ -195,47 +198,6 @@ namespace lms::audio::pulseaudio
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputStream::pause()
|
||||
{
|
||||
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Pausing stream");
|
||||
|
||||
MainLoopScopedLock lock{ _mainLoop };
|
||||
|
||||
pa_operation* op{ ::pa_stream_cork(_stream.get(), 1, nullptr, nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_cork (pause) failed", pa_context_errno(_context));
|
||||
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
|
||||
void AudioOutputStream::resume()
|
||||
{
|
||||
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Resuming stream");
|
||||
|
||||
MainLoopScopedLock lock{ _mainLoop };
|
||||
|
||||
{
|
||||
pa_operation* op{ ::pa_stream_cork(_stream.get(), 0, nullptr, nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_cork (resume) failed", pa_context_errno(_context));
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
|
||||
{
|
||||
pa_operation* op{ ::pa_stream_trigger(_stream.get(), NULL, NULL) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_trigger failed", pa_context_errno(_context));
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioOutputStream::isPaused() const
|
||||
{
|
||||
MainLoopScopedLock lock{ _mainLoop };
|
||||
|
||||
return pa_stream_is_corked(_stream.get());
|
||||
}
|
||||
|
||||
std::chrono::microseconds AudioOutputStream::getPlaybackTime() const
|
||||
{
|
||||
pa_usec_t duration{};
|
||||
@@ -279,6 +241,78 @@ namespace lms::audio::pulseaudio
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutputStream::pause()
|
||||
{
|
||||
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Pausing stream");
|
||||
|
||||
MainLoopScopedLock lock{ _mainLoop };
|
||||
|
||||
pa_operation* op{ ::pa_stream_cork(_stream.get(), 1, nullptr, nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_cork (pause) failed", pa_context_errno(_context));
|
||||
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
|
||||
void AudioOutputStream::resume()
|
||||
{
|
||||
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Resuming stream");
|
||||
|
||||
MainLoopScopedLock lock{ _mainLoop };
|
||||
|
||||
{
|
||||
pa_operation* op{ ::pa_stream_cork(_stream.get(), 0, nullptr, nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_cork (resume) failed", pa_context_errno(_context));
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
|
||||
{
|
||||
pa_operation* op{ ::pa_stream_trigger(_stream.get(), NULL, NULL) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_trigger failed", pa_context_errno(_context));
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioOutputStream::isPaused() const
|
||||
{
|
||||
MainLoopScopedLock lock{ _mainLoop };
|
||||
|
||||
return pa_stream_is_corked(_stream.get());
|
||||
}
|
||||
|
||||
void AudioOutputStream::setVolume(float volume)
|
||||
{
|
||||
MainLoopScopedLock lock{ _mainLoop };
|
||||
|
||||
if (volume < 0.F || volume > 1.F)
|
||||
throw Exception{ "Volume must be in range 0-1" };
|
||||
|
||||
const pa_volume_t paVol{ pa_sw_volume_from_linear(volume) };
|
||||
|
||||
pa_cvolume paChanVol;
|
||||
pa_cvolume_set(&paChanVol, _outputParameters.channelCount, paVol);
|
||||
|
||||
pa_operation* op{ ::pa_context_set_sink_input_volume(
|
||||
_context,
|
||||
pa_stream_get_index(_stream.get()),
|
||||
&paChanVol,
|
||||
nullptr,
|
||||
nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_context_set_sink_input_volume failed", pa_context_errno(_context));
|
||||
|
||||
::pa_operation_unref(op);
|
||||
|
||||
_currentVolume = volume;
|
||||
}
|
||||
|
||||
float AudioOutputStream::getVolume() const
|
||||
{
|
||||
return _currentVolume;
|
||||
}
|
||||
|
||||
void AudioOutputStream::connect()
|
||||
{
|
||||
constexpr pa_stream_flags_t flags{ static_cast<pa_stream_flags_t>(
|
||||
|
||||
@@ -62,15 +62,18 @@ namespace lms::audio::pulseaudio
|
||||
void asyncWrite(std::span<const std::byte> buffer, WriteCompletionCallback cb) override;
|
||||
void asyncDrain(DrainCompletionCallback cb) override;
|
||||
|
||||
void pause() override;
|
||||
void resume() override;
|
||||
bool isPaused() const override;
|
||||
|
||||
std::chrono::microseconds getPlaybackTime() const override;
|
||||
std::chrono::microseconds getLatency() const override;
|
||||
|
||||
void flush() override;
|
||||
|
||||
void pause() override;
|
||||
void resume() override;
|
||||
bool isPaused() const override;
|
||||
|
||||
void setVolume(float volume) override;
|
||||
float getVolume() const override;
|
||||
|
||||
void connect();
|
||||
void onStateChanged();
|
||||
void onWriteRequested(std::size_t writableSize);
|
||||
@@ -83,6 +86,7 @@ namespace lms::audio::pulseaudio
|
||||
pa_threaded_mainloop* _mainLoop;
|
||||
const PcmParameters _outputParameters;
|
||||
PaStreamPtr _stream;
|
||||
float _currentVolume{ 1.F };
|
||||
|
||||
WaitReadyCallback _waitReadyCallback;
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ namespace lms::audio
|
||||
virtual void pause() = 0;
|
||||
virtual void resume() = 0;
|
||||
virtual bool isPaused() const = 0;
|
||||
|
||||
virtual void setVolume(float volume) = 0;
|
||||
virtual float getVolume() const = 0;
|
||||
};
|
||||
|
||||
class IAudioOutputContext
|
||||
|
||||
@@ -118,6 +118,20 @@ namespace lms::jukebox
|
||||
return _outputStream->isPaused();
|
||||
}
|
||||
|
||||
void JukeboxService::setVolume(float volume)
|
||||
{
|
||||
std::unique_lock lock{ _mutex };
|
||||
|
||||
_outputStream->setVolume(volume);
|
||||
}
|
||||
|
||||
float JukeboxService::getVolume() const
|
||||
{
|
||||
std::shared_lock lock{ _mutex };
|
||||
|
||||
return _outputStream->getVolume();
|
||||
}
|
||||
|
||||
std::optional<std::size_t> JukeboxService::getCurrentTrackIndex() const
|
||||
{
|
||||
std::shared_lock lock{ _mutex };
|
||||
|
||||
@@ -51,6 +51,9 @@ namespace lms::jukebox
|
||||
void resume() override;
|
||||
bool isPaused() const override;
|
||||
|
||||
void setVolume(float volume) override;
|
||||
float getVolume() const override;
|
||||
|
||||
std::optional<std::size_t> getCurrentTrackIndex() const override;
|
||||
std::chrono::microseconds getPlaybackTrackTime() const override;
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ namespace lms::jukebox
|
||||
virtual void resume() = 0;
|
||||
virtual bool isPaused() const = 0;
|
||||
|
||||
virtual void setVolume(float volume) = 0; // from 0 to 1
|
||||
virtual float getVolume() const = 0;
|
||||
|
||||
virtual std::optional<std::size_t> getCurrentTrackIndex() const = 0; // may be unset if queue is cleared while playing
|
||||
virtual std::chrono::microseconds getPlaybackTrackTime() const = 0;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace lms::api::subsonic
|
||||
statusNode.setAttribute("currentIndex", jukeboxService.getCurrentTrackIndex() ? *jukeboxService.getCurrentTrackIndex() : -1); // required
|
||||
statusNode.setAttribute("playing", !jukeboxService.isPaused()); // required
|
||||
statusNode.setAttribute("position", std::chrono::duration_cast<std::chrono::seconds>(jukeboxService.getPlaybackTrackTime()).count());
|
||||
statusNode.setAttribute("gain", 1.f);
|
||||
statusNode.setAttribute("gain", jukeboxService.getVolume());
|
||||
|
||||
return statusNode;
|
||||
}
|
||||
@@ -158,6 +158,19 @@ namespace lms::api::subsonic
|
||||
return response;
|
||||
}
|
||||
|
||||
Response handleJukeboxSetGain(RequestContext& context, jukebox::IJukeboxService& jukeboxService)
|
||||
{
|
||||
const auto gain{ getMandatoryParameterAs<float>(context.getParameters(), "gain") };
|
||||
if (gain < 0 || gain > 1)
|
||||
throw BadParameterGenericError{ "gain", "gain must be between 0.0 and 1.0" };
|
||||
|
||||
jukeboxService.setVolume(gain); // consider gain is linear
|
||||
|
||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||
response.addNode("jukeboxStatus", createJukeboxStatusNode(jukeboxService));
|
||||
return response;
|
||||
}
|
||||
|
||||
using Actionhandler = std::function<Response(RequestContext& context, jukebox::IJukeboxService& jukeboxService)>;
|
||||
static const std::unordered_map<std::string, Actionhandler> actionHandlers{
|
||||
{ "get", detail::handleJukeboxGet },
|
||||
@@ -170,7 +183,7 @@ namespace lms::api::subsonic
|
||||
{ "clear", detail::handleJukeboxClear },
|
||||
{ "remove", detail::handleJukeboxRemove },
|
||||
{ "shuffle", detail::handleJukeboxShuffle },
|
||||
{ "setGain", detail::handleJukeboxStatus }, // not implemented
|
||||
{ "setGain", detail::handleJukeboxSetGain },
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
Reference in New Issue
Block a user