summaryrefslogtreecommitdiff
path: root/src/player
diff options
context:
space:
mode:
Diffstat (limited to 'src/player')
-rw-r--r--src/player/Control.cxx102
-rw-r--r--src/player/Control.hxx216
-rw-r--r--src/player/CrossFade.cxx2
-rw-r--r--src/player/Listener.hxx9
-rw-r--r--src/player/Outputs.hxx117
-rw-r--r--src/player/Thread.cxx580
6 files changed, 574 insertions, 452 deletions
diff --git a/src/player/Control.cxx b/src/player/Control.cxx
index 013b57bf5..4e4bdb00d 100644
--- a/src/player/Control.cxx
+++ b/src/player/Control.cxx
@@ -19,20 +19,20 @@
#include "config.h"
#include "Control.hxx"
+#include "Outputs.hxx"
#include "Idle.hxx"
#include "DetachedSong.hxx"
-#include "output/MultipleOutputs.hxx"
#include <algorithm>
#include <assert.h>
PlayerControl::PlayerControl(PlayerListener &_listener,
- MultipleOutputs &_outputs,
+ PlayerOutputs &_outputs,
unsigned _buffer_chunks,
unsigned _buffered_before_play,
AudioFormat _configured_audio_format,
- const ReplayGainConfig &_replay_gain_config)
+ const ReplayGainConfig &_replay_gain_config) noexcept
:listener(_listener), outputs(_outputs),
buffer_chunks(_buffer_chunks),
buffered_before_play(_buffered_before_play),
@@ -42,31 +42,30 @@ PlayerControl::PlayerControl(PlayerListener &_listener,
{
}
-PlayerControl::~PlayerControl()
+PlayerControl::~PlayerControl() noexcept
{
- delete next_song;
- delete tagged_song;
+ assert(!occupied);
}
bool
-PlayerControl::WaitOutputConsumed(unsigned threshold)
+PlayerControl::WaitOutputConsumed(unsigned threshold) noexcept
{
- bool result = outputs.Check() < threshold;
+ bool result = outputs.CheckPipe() < threshold;
if (!result && command == PlayerCommand::NONE) {
Wait();
- result = outputs.Check() < threshold;
+ result = outputs.CheckPipe() < threshold;
}
return result;
}
void
-PlayerControl::Play(DetachedSong *song)
+PlayerControl::Play(std::unique_ptr<DetachedSong> song)
{
assert(song != nullptr);
const std::lock_guard<Mutex> protect(mutex);
- SeekLocked(song, SongTime::zero());
+ SeekLocked(std::move(song), SongTime::zero());
if (state == PlayerState::PAUSE)
/* if the player was paused previously, we need to
@@ -75,14 +74,14 @@ PlayerControl::Play(DetachedSong *song)
}
void
-PlayerControl::LockCancel()
+PlayerControl::LockCancel() noexcept
{
LockSynchronousCommand(PlayerCommand::CANCEL);
assert(next_song == nullptr);
}
void
-PlayerControl::LockStop()
+PlayerControl::LockStop() noexcept
{
LockSynchronousCommand(PlayerCommand::CLOSE_AUDIO);
assert(next_song == nullptr);
@@ -91,13 +90,13 @@ PlayerControl::LockStop()
}
void
-PlayerControl::LockUpdateAudio()
+PlayerControl::LockUpdateAudio() noexcept
{
LockSynchronousCommand(PlayerCommand::UPDATE_AUDIO);
}
void
-PlayerControl::Kill()
+PlayerControl::Kill() noexcept
{
assert(thread.IsDefined());
@@ -108,7 +107,7 @@ PlayerControl::Kill()
}
void
-PlayerControl::PauseLocked()
+PlayerControl::PauseLocked() noexcept
{
if (state != PlayerState::STOP) {
SynchronousCommand(PlayerCommand::PAUSE);
@@ -117,14 +116,14 @@ PlayerControl::PauseLocked()
}
void
-PlayerControl::LockPause()
+PlayerControl::LockPause() noexcept
{
const std::lock_guard<Mutex> protect(mutex);
PauseLocked();
}
void
-PlayerControl::LockSetPause(bool pause_flag)
+PlayerControl::LockSetPause(bool pause_flag) noexcept
{
const std::lock_guard<Mutex> protect(mutex);
@@ -145,7 +144,7 @@ PlayerControl::LockSetPause(bool pause_flag)
}
void
-PlayerControl::LockSetBorderPause(bool _border_pause)
+PlayerControl::LockSetBorderPause(bool _border_pause) noexcept
{
const std::lock_guard<Mutex> protect(mutex);
border_pause = _border_pause;
@@ -157,7 +156,8 @@ PlayerControl::LockGetStatus() noexcept
player_status status;
const std::lock_guard<Mutex> protect(mutex);
- SynchronousCommand(PlayerCommand::REFRESH);
+ if (!occupied)
+ SynchronousCommand(PlayerCommand::REFRESH);
status.state = state;
@@ -172,7 +172,7 @@ PlayerControl::LockGetStatus() noexcept
}
void
-PlayerControl::SetError(PlayerError type, std::exception_ptr &&_error)
+PlayerControl::SetError(PlayerError type, std::exception_ptr &&_error) noexcept
{
assert(type != PlayerError::NONE);
assert(_error);
@@ -182,38 +182,61 @@ PlayerControl::SetError(PlayerError type, std::exception_ptr &&_error)
}
void
-PlayerControl::LockClearError()
+PlayerControl::LockClearError() noexcept
{
const std::lock_guard<Mutex> protect(mutex);
ClearError();
}
void
-PlayerControl::LockSetTaggedSong(const DetachedSong &song)
+PlayerControl::LockSetTaggedSong(const DetachedSong &song) noexcept
{
const std::lock_guard<Mutex> protect(mutex);
- delete tagged_song;
- tagged_song = new DetachedSong(song);
+ tagged_song.reset();
+ tagged_song = std::make_unique<DetachedSong>(song);
}
void
-PlayerControl::ClearTaggedSong()
+PlayerControl::ClearTaggedSong() noexcept
{
- delete tagged_song;
- tagged_song = nullptr;
+ tagged_song.reset();
+}
+
+std::unique_ptr<DetachedSong>
+PlayerControl::ReadTaggedSong() noexcept
+{
+ return std::exchange(tagged_song, nullptr);
+}
+
+std::unique_ptr<DetachedSong>
+PlayerControl::LockReadTaggedSong() noexcept
+{
+ const std::lock_guard<Mutex> protect(mutex);
+ return ReadTaggedSong();
}
void
-PlayerControl::LockEnqueueSong(DetachedSong *song)
+PlayerControl::LockEnqueueSong(std::unique_ptr<DetachedSong> song) noexcept
{
assert(song != nullptr);
const std::lock_guard<Mutex> protect(mutex);
- EnqueueSongLocked(song);
+ EnqueueSongLocked(std::move(song));
}
void
-PlayerControl::SeekLocked(DetachedSong *song, SongTime t)
+PlayerControl::EnqueueSongLocked(std::unique_ptr<DetachedSong> song) noexcept
+{
+ assert(song != nullptr);
+ assert(next_song == nullptr);
+
+ next_song = std::move(song);
+ seek_time = SongTime::zero();
+ SynchronousCommand(PlayerCommand::QUEUE);
+}
+
+void
+PlayerControl::SeekLocked(std::unique_ptr<DetachedSong> song, SongTime t)
{
assert(song != nullptr);
@@ -227,12 +250,17 @@ PlayerControl::SeekLocked(DetachedSong *song, SongTime t)
assert(next_song == nullptr);
ClearError();
- next_song = song;
+ next_song = std::move(song);
seek_time = t;
SynchronousCommand(PlayerCommand::SEEK);
assert(next_song == nullptr);
+ /* the SEEK command is asynchronous; until completion, the
+ "seeking" flag is set */
+ while (seeking)
+ ClientWait();
+
if (error_type != PlayerError::NONE) {
assert(error);
std::rethrow_exception(error);
@@ -242,20 +270,20 @@ PlayerControl::SeekLocked(DetachedSong *song, SongTime t)
}
void
-PlayerControl::LockSeek(DetachedSong *song, SongTime t)
+PlayerControl::LockSeek(std::unique_ptr<DetachedSong> song, SongTime t)
{
assert(song != nullptr);
{
const std::lock_guard<Mutex> protect(mutex);
- SeekLocked(song, t);
+ SeekLocked(std::move(song), t);
}
idle_add(IDLE_PLAYER);
}
void
-PlayerControl::SetCrossFade(float _cross_fade_seconds)
+PlayerControl::SetCrossFade(float _cross_fade_seconds) noexcept
{
if (_cross_fade_seconds < 0)
_cross_fade_seconds = 0;
@@ -265,7 +293,7 @@ PlayerControl::SetCrossFade(float _cross_fade_seconds)
}
void
-PlayerControl::SetMixRampDb(float _mixramp_db)
+PlayerControl::SetMixRampDb(float _mixramp_db) noexcept
{
cross_fade.mixramp_db = _mixramp_db;
@@ -273,7 +301,7 @@ PlayerControl::SetMixRampDb(float _mixramp_db)
}
void
-PlayerControl::SetMixRampDelay(float _mixramp_delay_seconds)
+PlayerControl::SetMixRampDelay(float _mixramp_delay_seconds) noexcept
{
cross_fade.mixramp_delay = _mixramp_delay_seconds;
diff --git a/src/player/Control.hxx b/src/player/Control.hxx
index 39e24e4f8..ebd063098 100644
--- a/src/player/Control.hxx
+++ b/src/player/Control.hxx
@@ -31,11 +31,12 @@
#include "ReplayGainMode.hxx"
#include <exception>
+#include <memory>
#include <stdint.h>
class PlayerListener;
-class MultipleOutputs;
+class PlayerOutputs;
class DetachedSong;
enum class PlayerState : uint8_t {
@@ -49,7 +50,16 @@ enum class PlayerCommand : uint8_t {
EXIT,
STOP,
PAUSE,
+
+ /**
+ * Seek to a certain position in the specified song. This
+ * command can also be used to change the current song or
+ * start playback. It "finishes" immediately, but
+ * PlayerControl::seeking will be set until seeking really
+ * completes (or fails).
+ */
SEEK,
+
CLOSE_AUDIO,
/**
@@ -100,7 +110,7 @@ struct player_status {
struct PlayerControl final : AudioOutputClient {
PlayerListener &listener;
- MultipleOutputs &outputs;
+ PlayerOutputs &outputs;
const unsigned buffer_chunks;
@@ -133,11 +143,6 @@ struct PlayerControl final : AudioOutputClient {
*/
Cond client_cond;
- PlayerCommand command = PlayerCommand::NONE;
- PlayerState state = PlayerState::STOP;
-
- PlayerError error_type = PlayerError::NONE;
-
/**
* The error that occurred in the player thread. This
* attribute is only valid if #error_type is not
@@ -147,6 +152,14 @@ struct PlayerControl final : AudioOutputClient {
std::exception_ptr error;
/**
+ * The next queued song.
+ *
+ * This is a duplicate, and must be freed when this attribute
+ * is cleared.
+ */
+ std::unique_ptr<DetachedSong> next_song;
+
+ /**
* A copy of the current #DetachedSong after its tags have
* been updated by the decoder (for example, a radio stream
* that has sent a new tag after switching to the next song).
@@ -156,29 +169,19 @@ struct PlayerControl final : AudioOutputClient {
* Protected by #mutex. Set by the PlayerThread and consumed
* by the main thread.
*/
- DetachedSong *tagged_song = nullptr;
+ std::unique_ptr<DetachedSong> tagged_song;
- uint16_t bit_rate;
- AudioFormat audio_format;
- SignedSongTime total_time;
- SongTime elapsed_time;
-
- /**
- * The next queued song.
- *
- * This is a duplicate, and must be freed when this attribute
- * is cleared.
- */
- DetachedSong *next_song = nullptr;
-
- SongTime seek_time;
+ PlayerCommand command = PlayerCommand::NONE;
+ PlayerState state = PlayerState::STOP;
- CrossFadeSettings cross_fade;
+ PlayerError error_type = PlayerError::NONE;
- const ReplayGainConfig replay_gain_config;
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
- double total_play_time = 0;
+ /**
+ * Is the player currently busy with the SEEK command?
+ */
+ bool seeking = false;
/**
* If this flag is set, then the player will be auto-paused at
@@ -189,25 +192,62 @@ struct PlayerControl final : AudioOutputClient {
*/
bool border_pause = false;
+ /**
+ * If this flag is set, then the player thread is currently
+ * occupied and will not be able to respond quickly to
+ * commands (e.g. waiting for the decoder thread to finish
+ * seeking). This is used to skip #PlayerCommand::REFRESH to
+ * avoid blocking the main thread.
+ */
+ bool occupied = false;
+
+ struct ScopeOccupied {
+ PlayerControl &pc;
+
+ explicit ScopeOccupied(PlayerControl &_pc) noexcept:pc(_pc) {
+ assert(!pc.occupied);
+ pc.occupied = true;
+ }
+
+ ~ScopeOccupied() noexcept {
+ assert(pc.occupied);
+ pc.occupied = false;
+ }
+ };
+
+ AudioFormat audio_format;
+ uint16_t bit_rate;
+
+ SignedSongTime total_time;
+ SongTime elapsed_time;
+
+ SongTime seek_time;
+
+ CrossFadeSettings cross_fade;
+
+ const ReplayGainConfig replay_gain_config;
+
+ double total_play_time = 0;
+
PlayerControl(PlayerListener &_listener,
- MultipleOutputs &_outputs,
+ PlayerOutputs &_outputs,
unsigned buffer_chunks,
unsigned buffered_before_play,
AudioFormat _configured_audio_format,
- const ReplayGainConfig &_replay_gain_config);
- ~PlayerControl();
+ const ReplayGainConfig &_replay_gain_config) noexcept;
+ ~PlayerControl() noexcept;
/**
* Locks the object.
*/
- void Lock() const {
+ void Lock() const noexcept {
mutex.lock();
}
/**
* Unlocks the object.
*/
- void Unlock() const {
+ void Unlock() const noexcept {
mutex.unlock();
}
@@ -215,7 +255,7 @@ struct PlayerControl final : AudioOutputClient {
* Signals the object. The object should be locked prior to
* calling this function.
*/
- void Signal() {
+ void Signal() noexcept {
cond.signal();
}
@@ -223,7 +263,7 @@ struct PlayerControl final : AudioOutputClient {
* Signals the object. The object is temporarily locked by
* this function.
*/
- void LockSignal() {
+ void LockSignal() noexcept {
const std::lock_guard<Mutex> protect(mutex);
Signal();
}
@@ -233,7 +273,7 @@ struct PlayerControl final : AudioOutputClient {
* valid in the player thread. The object must be locked
* prior to calling this function.
*/
- void Wait() {
+ void Wait() noexcept {
assert(thread.IsInside());
cond.wait(mutex);
@@ -244,7 +284,7 @@ struct PlayerControl final : AudioOutputClient {
*
* Caller must lock the object.
*/
- void ClientSignal() {
+ void ClientSignal() noexcept {
assert(thread.IsInside());
client_cond.signal();
@@ -256,7 +296,7 @@ struct PlayerControl final : AudioOutputClient {
*
* Caller must lock the object.
*/
- void ClientWait() {
+ void ClientWait() noexcept {
assert(!thread.IsInside());
client_cond.wait(mutex);
@@ -269,14 +309,14 @@ struct PlayerControl final : AudioOutputClient {
* To be called from the player thread. Caller must lock the
* object.
*/
- void CommandFinished() {
+ void CommandFinished() noexcept {
assert(command != PlayerCommand::NONE);
command = PlayerCommand::NONE;
ClientSignal();
}
- void LockCommandFinished() {
+ void LockCommandFinished() noexcept {
const std::lock_guard<Mutex> protect(mutex);
CommandFinished();
}
@@ -291,9 +331,9 @@ struct PlayerControl final : AudioOutputClient {
* @param threshold the maximum number of chunks in the pipe
* @return true if there are less than #threshold chunks in the pipe
*/
- bool WaitOutputConsumed(unsigned threshold);
+ bool WaitOutputConsumed(unsigned threshold) noexcept;
- bool LockWaitOutputConsumed(unsigned threshold) {
+ bool LockWaitOutputConsumed(unsigned threshold) noexcept {
const std::lock_guard<Mutex> protect(mutex);
return WaitOutputConsumed(threshold);
}
@@ -305,7 +345,7 @@ private:
* To be called from the main thread. Caller must lock the
* object.
*/
- void WaitCommandLocked() {
+ void WaitCommandLocked() noexcept {
while (command != PlayerCommand::NONE)
ClientWait();
}
@@ -339,53 +379,47 @@ private:
public:
/**
- * Throws std::runtime_error or #Error on error.
+ * Throws on error.
*
- * @param song the song to be queued; the given instance will
- * be owned and freed by the player
+ * @param song the song to be queued
*/
- void Play(DetachedSong *song);
+ void Play(std::unique_ptr<DetachedSong> song);
/**
* see PlayerCommand::CANCEL
*/
- void LockCancel();
+ void LockCancel() noexcept;
- void LockSetPause(bool pause_flag);
+ void LockSetPause(bool pause_flag) noexcept;
private:
- void PauseLocked();
+ void PauseLocked() noexcept;
- void ClearError() {
+ void ClearError() noexcept {
error_type = PlayerError::NONE;
error = std::exception_ptr();
}
public:
- void LockPause();
+ void LockPause() noexcept;
/**
* Set the player's #border_pause flag.
*/
- void LockSetBorderPause(bool border_pause);
+ void LockSetBorderPause(bool border_pause) noexcept;
- bool ApplyBorderPause() {
+ bool ApplyBorderPause() noexcept {
if (border_pause)
state = PlayerState::PAUSE;
return border_pause;
}
- bool LockApplyBorderPause() {
- const std::lock_guard<Mutex> lock(mutex);
- return ApplyBorderPause();
- }
-
- void Kill();
+ void Kill() noexcept;
gcc_pure
player_status LockGetStatus() noexcept;
- PlayerState GetState() const {
+ PlayerState GetState() const noexcept {
return state;
}
@@ -396,12 +430,12 @@ public:
*
* @param type the error type; must not be #PlayerError::NONE
*/
- void SetError(PlayerError type, std::exception_ptr &&_error);
+ void SetError(PlayerError type, std::exception_ptr &&_error) noexcept;
/**
* Set the error and set state to PlayerState::PAUSE.
*/
- void SetOutputError(std::exception_ptr &&_error) {
+ void SetOutputError(std::exception_ptr &&_error) noexcept {
SetError(PlayerError::OUTPUT, std::move(_error));
/* pause: the user may resume playback as soon as an
@@ -409,7 +443,7 @@ public:
state = PlayerState::PAUSE;
}
- void LockSetOutputError(std::exception_ptr &&_error) {
+ void LockSetOutputError(std::exception_ptr &&_error) noexcept {
const std::lock_guard<Mutex> lock(mutex);
SetOutputError(std::move(_error));
}
@@ -433,9 +467,9 @@ public:
CheckRethrowError();
}
- void LockClearError();
+ void LockClearError() noexcept;
- PlayerError GetErrorType() const {
+ PlayerError GetErrorType() const noexcept {
return error_type;
}
@@ -443,89 +477,75 @@ public:
* Set the #tagged_song attribute to a newly allocated copy of
* the given #DetachedSong. Locks and unlocks the object.
*/
- void LockSetTaggedSong(const DetachedSong &song);
+ void LockSetTaggedSong(const DetachedSong &song) noexcept;
- void ClearTaggedSong();
+ void ClearTaggedSong() noexcept;
/**
* Read and clear the #tagged_song attribute.
*
* Caller must lock the object.
*/
- DetachedSong *ReadTaggedSong() {
- DetachedSong *result = tagged_song;
- tagged_song = nullptr;
- return result;
- }
+ std::unique_ptr<DetachedSong> ReadTaggedSong() noexcept;
/**
* Like ReadTaggedSong(), but locks and unlocks the object.
*/
- DetachedSong *LockReadTaggedSong() {
- const std::lock_guard<Mutex> protect(mutex);
- return ReadTaggedSong();
- }
+ std::unique_ptr<DetachedSong> LockReadTaggedSong() noexcept;
- void LockStop();
+ void LockStop() noexcept;
- void LockUpdateAudio();
+ void LockUpdateAudio() noexcept;
private:
- void EnqueueSongLocked(DetachedSong *song) {
- assert(song != nullptr);
- assert(next_song == nullptr);
-
- next_song = song;
- seek_time = SongTime::zero();
- SynchronousCommand(PlayerCommand::QUEUE);
- }
+ void EnqueueSongLocked(std::unique_ptr<DetachedSong> song) noexcept;
/**
- * Throws std::runtime_error or #Error on error.
+ * Throws on error.
*/
- void SeekLocked(DetachedSong *song, SongTime t);
+ void SeekLocked(std::unique_ptr<DetachedSong> song, SongTime t);
public:
/**
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
- void LockEnqueueSong(DetachedSong *song);
+ void LockEnqueueSong(std::unique_ptr<DetachedSong> song) noexcept;
/**
* Makes the player thread seek the specified song to a position.
*
- * Throws std::runtime_error or #Error on error.
+ * Throws on error.
*
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
- void LockSeek(DetachedSong *song, SongTime t);
+ void LockSeek(std::unique_ptr<DetachedSong> song, SongTime t);
- void SetCrossFade(float cross_fade_seconds);
+ void SetCrossFade(float cross_fade_seconds) noexcept;
- float GetCrossFade() const {
+ float GetCrossFade() const noexcept {
return cross_fade.duration;
}
- void SetMixRampDb(float mixramp_db);
+ void SetMixRampDb(float mixramp_db) noexcept;
- float GetMixRampDb() const {
+ float GetMixRampDb() const noexcept {
return cross_fade.mixramp_db;
}
- void SetMixRampDelay(float mixramp_delay_seconds);
+ void SetMixRampDelay(float mixramp_delay_seconds) noexcept;
- float GetMixRampDelay() const {
+ float GetMixRampDelay() const noexcept {
return cross_fade.mixramp_delay;
}
- void LockSetReplayGainMode(ReplayGainMode _mode) {
+ void LockSetReplayGainMode(ReplayGainMode _mode) noexcept {
const std::lock_guard<Mutex> protect(mutex);
replay_gain_mode = _mode;
}
- double GetTotalPlayTime() const {
+ double GetTotalPlayTime() const noexcept {
return total_play_time;
}
@@ -539,7 +559,7 @@ public:
}
private:
- void RunThread();
+ void RunThread() noexcept;
};
#endif
diff --git a/src/player/CrossFade.cxx b/src/player/CrossFade.cxx
index 3db5932c0..903adf410 100644
--- a/src/player/CrossFade.cxx
+++ b/src/player/CrossFade.cxx
@@ -105,7 +105,7 @@ CrossFadeSettings::Calculate(SignedSongTime total_time,
assert(duration >= 0);
assert(af.IsValid());
- chunks_f = (float)af.GetTimeToSize() / (float)CHUNK_SIZE;
+ chunks_f = (float)af.GetTimeToSize() / (float)sizeof(MusicChunk::data);
if (mixramp_delay <= 0 || !mixramp_start || !mixramp_prev_end) {
chunks = (chunks_f * duration + 0.5);
diff --git a/src/player/Listener.hxx b/src/player/Listener.hxx
index 65f2bb598..1f0bdd413 100644
--- a/src/player/Listener.hxx
+++ b/src/player/Listener.hxx
@@ -25,12 +25,17 @@ public:
/**
* Must call playlist_sync().
*/
- virtual void OnPlayerSync() = 0;
+ virtual void OnPlayerSync() noexcept = 0;
/**
* The current song's tag has changed.
*/
- virtual void OnPlayerTagModified() = 0;
+ virtual void OnPlayerTagModified() noexcept = 0;
+
+ /**
+ * Playback went into border pause.
+ */
+ virtual void OnBorderPause() noexcept = 0;
};
#endif
diff --git a/src/player/Outputs.hxx b/src/player/Outputs.hxx
new file mode 100644
index 000000000..9b896ce5d
--- /dev/null
+++ b/src/player/Outputs.hxx
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2003-2017 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_PLAYER_OUTPUT_INTERFACE_HXX
+#define MPD_PLAYER_OUTPUT_INTERFACE_HXX
+
+#include "Chrono.hxx"
+#include "Compiler.h"
+
+struct AudioFormat;
+struct MusicChunk;
+class MusicBuffer;
+
+/**
+ * An interface for the player thread to control all outputs. This
+ * interface is implemented only by #MultipleOutputs, and exists only
+ * to decouple the player code from the output code, to be able to
+ * unit-test the player code.
+ */
+class PlayerOutputs {
+public:
+ /**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ *
+ * Throws on error.
+ */
+ virtual void EnableDisable() = 0;
+
+ /**
+ * Opens all audio outputs which are not disabled.
+ *
+ * Throws on error.
+ *
+ * @param audio_format the preferred audio format
+ * @param buffer the #MusicBuffer where consumed #MusicChunk
+ * objects should be returned
+ */
+ virtual void Open(const AudioFormat audio_format,
+ MusicBuffer &buffer) = 0;
+
+ /**
+ * Closes all audio outputs.
+ */
+ virtual void Close() noexcept = 0;
+
+ /**
+ * Closes all audio outputs. Outputs with the "always_on"
+ * flag are put into pause mode.
+ */
+ virtual void Release() noexcept = 0;
+
+ /**
+ * Enqueue a #MusicChunk object for playing, i.e. pushes it to a
+ * #MusicPipe.
+ *
+ * Throws on error (all closed then).
+ *
+ * @param chunk the #MusicChunk object to be played
+ */
+ virtual void Play(MusicChunk *chunk) = 0;
+
+ /**
+ * Checks if the output devices have drained their music pipe, and
+ * returns the consumed music chunks to the #music_buffer.
+ *
+ * @return the number of chunks to play left in the #MusicPipe
+ */
+ virtual unsigned CheckPipe() noexcept = 0;
+
+ /**
+ * Puts all audio outputs into pause mode. Most implementations will
+ * simply close it then.
+ */
+ virtual void Pause() noexcept = 0;
+
+ /**
+ * Drain all audio outputs.
+ */
+ virtual void Drain() noexcept = 0;
+
+ /**
+ * Try to cancel data which may still be in the device's buffers.
+ */
+ virtual void Cancel() noexcept = 0;
+
+ /**
+ * Indicate that a new song will begin now.
+ */
+ virtual void SongBorder() noexcept = 0;
+
+ /**
+ * Returns the "elapsed_time" stamp of the most recently finished
+ * chunk. A negative value is returned when no chunk has been
+ * finished yet.
+ */
+ gcc_pure
+ virtual SignedSongTime GetElapsedTime() const noexcept = 0;
+};
+
+#endif
diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx
index 651117088..c599b1725 100644
--- a/src/player/Thread.cxx
+++ b/src/player/Thread.cxx
@@ -19,25 +19,23 @@
#include "config.h"
#include "Thread.hxx"
+#include "Outputs.hxx"
#include "Listener.hxx"
#include "decoder/DecoderThread.hxx"
#include "decoder/DecoderControl.hxx"
#include "MusicPipe.hxx"
#include "MusicBuffer.hxx"
#include "MusicChunk.hxx"
-#include "pcm/Silence.hxx"
#include "DetachedSong.hxx"
#include "CrossFade.hxx"
#include "Control.hxx"
-#include "output/MultipleOutputs.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
-#include "system/PeriodClock.hxx"
#include "util/Domain.hxx"
#include "thread/Name.hxx"
#include "Log.hxx"
-#include <stdexcept>
+#include <exception>
#include <string.h>
@@ -53,31 +51,43 @@ class Player {
MusicPipe *pipe;
/**
+ * the song currently being played
+ */
+ std::unique_ptr<DetachedSong> song;
+
+ /**
+ * The tag of the "next" song during cross-fade. It is
+ * postponed, and sent to the output thread when the new song
+ * really begins.
+ */
+ std::unique_ptr<Tag> cross_fade_tag;
+
+ /**
* are we waiting for buffered_before_play?
*/
- bool buffering;
+ bool buffering = true;
/**
* true if the decoder is starting and did not provide data
* yet
*/
- bool decoder_starting;
+ bool decoder_starting = false;
/**
* Did we wake up the DecoderThread recently? This avoids
* duplicate wakeup calls.
*/
- bool decoder_woken;
+ bool decoder_woken = false;
/**
* is the player paused?
*/
- bool paused;
+ bool paused = false;
/**
* is there a new song in pc.next_song?
*/
- bool queued;
+ bool queued = true;
/**
* Was any audio output opened successfully? It might have
@@ -85,12 +95,7 @@ class Player {
* player thread. When this flag is unset, some output
* methods must not be called.
*/
- bool output_open;
-
- /**
- * the song currently being played
- */
- DetachedSong *song;
+ bool output_open = false;
/**
* Is cross-fading to the next song enabled?
@@ -119,19 +124,12 @@ class Player {
* Currently cross-fading to the next song.
*/
ACTIVE,
- } xfade_state;
+ } xfade_state = CrossFadeState::UNKNOWN;
/**
* The number of chunks used for crossfading.
*/
- unsigned cross_fade_chunks;
-
- /**
- * The tag of the "next" song during cross-fade. It is
- * postponed, and sent to the output thread when the new song
- * really begins.
- */
- Tag *cross_fade_tag;
+ unsigned cross_fade_chunks = 0;
/**
* The current audio format for the audio outputs.
@@ -145,47 +143,44 @@ class Player {
* value; the output thread can estimate the elapsed time more
* precisely.
*/
- SongTime elapsed_time;
+ SongTime elapsed_time = SongTime::zero();
- PeriodClock throttle_silence_log;
+ /**
+ * If this is positive, then we need to ask the decoder to
+ * seek after it has completed startup. This is needed if the
+ * decoder is in the middle of startup while the player
+ * receives another seek command.
+ *
+ * This is only valid while #decoder_starting is true.
+ */
+ SongTime pending_seek;
public:
Player(PlayerControl &_pc, DecoderControl &_dc,
- MusicBuffer &_buffer)
- :pc(_pc), dc(_dc), buffer(_buffer),
- buffering(true),
- decoder_starting(false),
- decoder_woken(false),
- paused(false),
- queued(true),
- output_open(false),
- song(nullptr),
- xfade_state(CrossFadeState::UNKNOWN),
- cross_fade_chunks(0),
- cross_fade_tag(nullptr),
- elapsed_time(SongTime::zero()) {}
+ MusicBuffer &_buffer) noexcept
+ :pc(_pc), dc(_dc), buffer(_buffer) {}
private:
/**
* Reset cross-fading to the initial state. A check to
* re-enable it at an appropriate time will be scheduled.
*/
- void ResetCrossFade() {
+ void ResetCrossFade() noexcept {
xfade_state = CrossFadeState::UNKNOWN;
}
- void ClearAndDeletePipe() {
+ void ClearAndDeletePipe() noexcept {
pipe->Clear(buffer);
delete pipe;
}
- void ClearAndReplacePipe(MusicPipe *_pipe) {
+ void ClearAndReplacePipe(MusicPipe *_pipe) noexcept {
ResetCrossFade();
ClearAndDeletePipe();
pipe = _pipe;
}
- void ReplacePipe(MusicPipe *_pipe) {
+ void ReplacePipe(MusicPipe *_pipe) noexcept {
ResetCrossFade();
delete pipe;
pipe = _pipe;
@@ -194,54 +189,29 @@ private:
/**
* Start the decoder.
*
- * Player lock is not held.
+ * Caller must lock the mutex.
*/
- void StartDecoder(MusicPipe &pipe);
+ void StartDecoder(MusicPipe &pipe) noexcept;
/**
* The decoder has acknowledged the "START" command (see
* ActivateDecoder()). This function checks if the decoder
- * initialization has completed yet.
+ * initialization has completed yet. If not, it will wait
+ * some more.
*
* Caller must lock the mutex.
- */
- bool CheckDecoderStartup();
-
- /**
- * Call CheckDecoderStartup() repeatedly until the decoder has
- * finished startup. Returns false on decoder error (and
- * finishes the #PlayerCommand).
*
- * This method does not check for commands. It is only
- * allowed to be used while a command is being handled.
+ * @return false if the decoder has failed, true on success
+ * (though the decoder startup may or may not yet be finished)
*/
- bool WaitDecoderStartup() {
- const std::lock_guard<Mutex> lock(pc.mutex);
-
- while (decoder_starting) {
- if (!CheckDecoderStartup()) {
- /* if decoder startup fails, make sure
- the previous song is not being
- played anymore */
- {
- const ScopeUnlock unlock(pc.mutex);
- pc.outputs.Cancel();
- }
-
- pc.CommandFinished();
- return false;
- }
- }
-
- return true;
- }
+ bool CheckDecoderStartup() noexcept;
/**
* Stop the decoder and clears (and frees) its music pipe.
*
- * Player lock is not held.
+ * Caller must lock the mutex.
*/
- void StopDecoder();
+ void StopDecoder() noexcept;
/**
* Is the decoder still busy on the same song as the player?
@@ -267,11 +237,32 @@ private:
}
/**
+ * Invoke DecoderControl::Seek() and update our state or
+ * handle errors.
+ *
+ * Caller must lock the mutex.
+ *
+ * @return false if the decoder has failed
+ */
+ bool SeekDecoder(SongTime seek_time) noexcept;
+
+ /**
* This is the handler for the #PlayerCommand::SEEK command.
*
- * The player lock is not held.
+ * Caller must lock the mutex.
+ *
+ * @return false if the decoder has failed
*/
- bool SeekDecoder();
+ bool SeekDecoder() noexcept;
+
+ void CancelPendingSeek() noexcept {
+ if (!pc.seeking)
+ return;
+
+ pending_seek = SongTime::zero();
+ pc.seeking = false;
+ pc.ClientSignal();
+ }
/**
* Check if the decoder has reported an error, and forward it
@@ -279,7 +270,7 @@ private:
*
* @return false if an error has occurred
*/
- bool ForwardDecoderError();
+ bool ForwardDecoderError() noexcept;
/**
* After the decoder has been started asynchronously, activate
@@ -292,9 +283,9 @@ private:
* yet, therefore we don't know the audio format yet. To
* finish decoder startup, call CheckDecoderStartup().
*
- * The player lock is not held.
+ * Caller must lock the mutex.
*/
- void ActivateDecoder();
+ void ActivateDecoder() noexcept;
/**
* Wrapper for MultipleOutputs::Open(). Upon failure, it
@@ -304,7 +295,7 @@ private:
*
* @return true on success
*/
- bool OpenOutput();
+ bool OpenOutput() noexcept;
/**
* Obtains the next chunk from the music pipe, optionally applies
@@ -312,30 +303,28 @@ private:
*
* @return true on success, false on error (playback will be stopped)
*/
- bool PlayNextChunk();
+ bool PlayNextChunk() noexcept;
- /**
- * Sends a chunk of silence to the audio outputs. This is
- * called when there is not enough decoded data in the pipe
- * yet, to prevent underruns in the hardware buffers.
- *
- * The player lock is not held.
- */
- bool SendSilence();
+ unsigned UnlockCheckOutputs() noexcept {
+ const ScopeUnlock unlock(pc.mutex);
+ return pc.outputs.CheckPipe();
+ }
/**
* Player lock must be held before calling.
+ *
+ * @return false to stop playback
*/
- void ProcessCommand();
+ bool ProcessCommand() noexcept;
/**
* This is called at the border between two songs: the audio output
* has consumed all chunks of the current song, and we should start
* sending chunks from the next one.
*
- * The player lock is not held.
+ * Caller must lock the mutex.
*/
- void SongBorder();
+ void SongBorder() noexcept;
public:
/*
@@ -343,31 +332,30 @@ public:
* is basically a state machine, which multiplexes data
* between the decoder thread and the output threads.
*/
- void Run();
+ void Run() noexcept;
};
void
-Player::StartDecoder(MusicPipe &_pipe)
+Player::StartDecoder(MusicPipe &_pipe) noexcept
{
assert(queued || pc.command == PlayerCommand::SEEK);
assert(pc.next_song != nullptr);
- {
- /* copy ReplayGain parameters to the decoder */
- const std::lock_guard<Mutex> protect(pc.mutex);
- dc.replay_gain_mode = pc.replay_gain_mode;
- }
+ /* copy ReplayGain parameters to the decoder */
+ dc.replay_gain_mode = pc.replay_gain_mode;
SongTime start_time = pc.next_song->GetStartTime() + pc.seek_time;
- dc.Start(new DetachedSong(*pc.next_song),
+ dc.Start(std::make_unique<DetachedSong>(*pc.next_song),
start_time, pc.next_song->GetEndTime(),
buffer, _pipe);
}
void
-Player::StopDecoder()
+Player::StopDecoder() noexcept
{
+ const PlayerControl::ScopeOccupied occupied(pc);
+
dc.Stop();
if (dc.pipe != nullptr) {
@@ -388,7 +376,7 @@ Player::StopDecoder()
}
bool
-Player::ForwardDecoderError()
+Player::ForwardDecoderError() noexcept
{
try {
dc.CheckRethrowError();
@@ -401,36 +389,34 @@ Player::ForwardDecoderError()
}
void
-Player::ActivateDecoder()
+Player::ActivateDecoder() noexcept
{
assert(queued || pc.command == PlayerCommand::SEEK);
assert(pc.next_song != nullptr);
queued = false;
- {
- const std::lock_guard<Mutex> lock(pc.mutex);
+ pc.ClearTaggedSong();
- pc.ClearTaggedSong();
+ song = std::exchange(pc.next_song, nullptr);
- delete song;
- song = pc.next_song;
- pc.next_song = nullptr;
+ elapsed_time = pc.seek_time;
- elapsed_time = pc.seek_time;
+ /* set the "starting" flag, which will be cleared by
+ CheckDecoderStartup() */
+ decoder_starting = true;
+ pending_seek = SongTime::zero();
- /* set the "starting" flag, which will be cleared by
- player_check_decoder_startup() */
- decoder_starting = true;
+ /* update PlayerControl's song information */
+ pc.total_time = song->GetDuration();
+ pc.bit_rate = 0;
+ pc.audio_format.Clear();
- /* update PlayerControl's song information */
- pc.total_time = song->GetDuration();
- pc.bit_rate = 0;
- pc.audio_format.Clear();
+ {
+ /* call syncPlaylistWithQueue() in the main thread */
+ const ScopeUnlock unlock(pc.mutex);
+ pc.listener.OnPlayerSync();
}
-
- /* call syncPlaylistWithQueue() in the main thread */
- pc.listener.OnPlayerSync();
}
/**
@@ -438,7 +424,8 @@ Player::ActivateDecoder()
* indicated by the decoder plugin.
*/
static SignedSongTime
-real_song_duration(const DetachedSong &song, SignedSongTime decoder_duration)
+real_song_duration(const DetachedSong &song,
+ SignedSongTime decoder_duration) noexcept
{
if (decoder_duration.IsNegative())
/* the decoder plugin didn't provide information; fall
@@ -455,7 +442,7 @@ real_song_duration(const DetachedSong &song, SignedSongTime decoder_duration)
}
bool
-Player::OpenOutput()
+Player::OpenOutput() noexcept
{
assert(play_audio_format.IsDefined());
assert(pc.state == PlayerState::PLAY ||
@@ -464,8 +451,8 @@ Player::OpenOutput()
try {
const ScopeUnlock unlock(pc.mutex);
pc.outputs.Open(play_audio_format, buffer);
- } catch (const std::runtime_error &e) {
- LogError(e);
+ } catch (...) {
+ LogError(std::current_exception());
output_open = false;
@@ -491,7 +478,7 @@ Player::OpenOutput()
}
bool
-Player::CheckDecoderStartup()
+Player::CheckDecoderStartup() noexcept
{
assert(decoder_starting);
@@ -515,6 +502,25 @@ Player::CheckDecoderStartup()
idle_add(IDLE_PLAYER);
+ if (pending_seek > SongTime::zero()) {
+ assert(pc.seeking);
+
+ bool success = SeekDecoder(pending_seek);
+ pc.seeking = false;
+ pc.ClientSignal();
+ if (!success)
+ return false;
+
+ /* re-fill the buffer after seeking */
+ buffering = true;
+ } else if (pc.seeking) {
+ pc.seeking = false;
+ pc.ClientSignal();
+
+ /* re-fill the buffer after seeking */
+ buffering = true;
+ }
+
if (!paused && !OpenOutput()) {
FormatError(player_domain,
"problems opening audio device "
@@ -534,57 +540,44 @@ Player::CheckDecoderStartup()
}
bool
-Player::SendSilence()
+Player::SeekDecoder(SongTime seek_time) noexcept
{
- assert(output_open);
- assert(play_audio_format.IsDefined());
+ assert(song);
+ assert(!decoder_starting);
- MusicChunk *chunk = buffer.Allocate();
- if (chunk == nullptr) {
- /* this is non-fatal, because this means that the
- decoder has filled to buffer completely meanwhile;
- by ignoring the error, we work around this race
- condition */
- LogDebug(player_domain, "Failed to allocate silence buffer");
- return true;
+ if (!pc.total_time.IsNegative()) {
+ const SongTime total_time(pc.total_time);
+ if (seek_time > total_time)
+ seek_time = total_time;
}
-#ifndef NDEBUG
- chunk->audio_format = play_audio_format;
-#endif
-
- const size_t frame_size = play_audio_format.GetFrameSize();
- /* this formula ensures that we don't send
- partial frames */
- unsigned num_frames = sizeof(chunk->data) / frame_size;
-
- chunk->bit_rate = 0;
- chunk->time = SignedSongTime::Negative(); /* undefined time stamp */
- chunk->length = num_frames * frame_size;
- chunk->replay_gain_serial = MusicChunk::IGNORE_REPLAY_GAIN;
- PcmSilence({chunk->data, chunk->length}, play_audio_format.format);
-
try {
- pc.outputs.Play(chunk);
- } catch (const std::runtime_error &e) {
- LogError(e);
- buffer.Return(chunk);
+ const PlayerControl::ScopeOccupied occupied(pc);
+
+ dc.Seek(song->GetStartTime() + seek_time);
+ } catch (...) {
+ /* decoder failure */
+ pc.SetError(PlayerError::DECODER, std::current_exception());
return false;
}
+ elapsed_time = seek_time;
return true;
}
inline bool
-Player::SeekDecoder()
+Player::SeekDecoder() noexcept
{
assert(pc.next_song != nullptr);
- pc.outputs.Cancel();
+ CancelPendingSeek();
- const SongTime start_time = pc.next_song->GetStartTime();
+ {
+ const ScopeUnlock unlock(pc.mutex);
+ pc.outputs.Cancel();
+ }
- if (!dc.LockIsSeeakbleCurrentSong(*pc.next_song)) {
+ if (!dc.IsSeekableCurrentSong(*pc.next_song)) {
/* the decoder is already decoding the "next" song -
stop it and start the previous song again */
@@ -598,8 +591,12 @@ Player::SeekDecoder()
StartDecoder(*pipe);
ActivateDecoder();
- if (!WaitDecoderStartup())
- return false;
+ pc.seeking = true;
+ pc.CommandFinished();
+
+ assert(xfade_state == CrossFadeState::UNKNOWN);
+
+ return true;
} else {
if (!IsDecoderAtCurrentSong()) {
/* the decoder is already decoding the "next" song,
@@ -607,40 +604,29 @@ Player::SeekDecoder()
ClearAndReplacePipe(dc.pipe);
}
- delete pc.next_song;
- pc.next_song = nullptr;
+ pc.next_song.reset();
queued = false;
- /* wait for the decoder to complete initialization
- (just in case that happens to be still in
- progress) */
-
- if (!WaitDecoderStartup())
- return false;
-
- /* send the SEEK command */
+ if (decoder_starting) {
+ /* wait for the decoder to complete
+ initialization; postpone the SEEK
+ command */
- SongTime where = pc.seek_time;
- if (!pc.total_time.IsNegative()) {
- const SongTime total_time(pc.total_time);
- if (where > total_time)
- where = total_time;
- }
+ pending_seek = pc.seek_time;
+ pc.seeking = true;
+ pc.CommandFinished();
+ return true;
+ } else {
+ /* send the SEEK command */
- try {
- dc.Seek(where + start_time);
- } catch (...) {
- /* decoder failure */
- pc.SetError(PlayerError::DECODER,
- std::current_exception());
- pc.LockCommandFinished();
- return false;
+ if (!SeekDecoder(pc.seek_time)) {
+ pc.CommandFinished();
+ return false;
+ }
}
-
- elapsed_time = where;
}
- pc.LockCommandFinished();
+ pc.CommandFinished();
assert(xfade_state == CrossFadeState::UNKNOWN);
@@ -650,15 +636,17 @@ Player::SeekDecoder()
return true;
}
-inline void
-Player::ProcessCommand()
+inline bool
+Player::ProcessCommand() noexcept
{
switch (pc.command) {
case PlayerCommand::NONE:
+ break;
+
case PlayerCommand::STOP:
case PlayerCommand::EXIT:
case PlayerCommand::CLOSE_AUDIO:
- break;
+ return false;
case PlayerCommand::UPDATE_AUDIO:
{
@@ -677,11 +665,8 @@ Player::ProcessCommand()
queued = true;
pc.CommandFinished();
- {
- const ScopeUnlock unlock(pc.mutex);
- if (dc.LockIsIdle())
- StartDecoder(*new MusicPipe());
- }
+ if (dc.IsIdle())
+ StartDecoder(*new MusicPipe());
break;
@@ -704,30 +689,21 @@ Player::ProcessCommand()
break;
case PlayerCommand::SEEK:
- {
- const ScopeUnlock unlock(pc.mutex);
- SeekDecoder();
- }
- break;
+ return SeekDecoder();
case PlayerCommand::CANCEL:
- if (pc.next_song == nullptr) {
+ if (pc.next_song == nullptr)
/* the cancel request arrived too late, we're
already playing the queued song... stop
everything now */
- pc.command = PlayerCommand::STOP;
- return;
- }
+ return false;
- if (IsDecoderAtNextSong()) {
+ if (IsDecoderAtNextSong())
/* the decoder is already decoding the song -
stop it and reset the position */
- const ScopeUnlock unlock(pc.mutex);
StopDecoder();
- }
- delete pc.next_song;
- pc.next_song = nullptr;
+ pc.next_song.reset();
queued = false;
pc.CommandFinished();
break;
@@ -735,7 +711,7 @@ Player::ProcessCommand()
case PlayerCommand::REFRESH:
if (output_open && !paused) {
const ScopeUnlock unlock(pc.mutex);
- pc.outputs.Check();
+ pc.outputs.CheckPipe();
}
pc.elapsed_time = !pc.outputs.GetElapsedTime().IsNegative()
@@ -745,10 +721,13 @@ Player::ProcessCommand()
pc.CommandFinished();
break;
}
+
+ return true;
}
static void
-update_song_tag(PlayerControl &pc, DetachedSong &song, const Tag &new_tag)
+update_song_tag(PlayerControl &pc, DetachedSong &song,
+ const Tag &new_tag) noexcept
{
if (song.IsFile())
/* don't update tags of local files, only remote
@@ -804,7 +783,7 @@ play_chunk(PlayerControl &pc,
}
inline bool
-Player::PlayNextChunk()
+Player::PlayNextChunk() noexcept
{
if (!pc.LockWaitOutputConsumed(64))
/* the output pipe is still large enough, don't send
@@ -840,10 +819,8 @@ Player::PlayNextChunk()
/* don't send the tags of the new song (which
is being faded in) yet; postpone it until
the current song is faded out */
- cross_fade_tag =
- Tag::MergeReplace(cross_fade_tag,
- other_chunk->tag);
- other_chunk->tag = nullptr;
+ cross_fade_tag = Tag::Merge(std::move(cross_fade_tag),
+ std::move(other_chunk->tag));
if (pc.cross_fade.mixramp_delay <= 0) {
chunk->mix_ratio = ((float)cross_fade_position)
@@ -892,7 +869,8 @@ Player::PlayNextChunk()
/* insert the postponed tag if cross-fading is finished */
if (xfade_state != CrossFadeState::ACTIVE && cross_fade_tag != nullptr) {
- chunk->tag = Tag::MergeReplace(chunk->tag, cross_fade_tag);
+ chunk->tag = Tag::Merge(std::move(chunk->tag),
+ std::move(cross_fade_tag));
cross_fade_tag = nullptr;
}
@@ -900,8 +878,8 @@ Player::PlayNextChunk()
try {
play_chunk(pc, *song, chunk, buffer, play_audio_format);
- } catch (const std::runtime_error &e) {
- LogError(e);
+ } catch (...) {
+ LogError(std::current_exception());
buffer.Return(chunk);
@@ -935,49 +913,45 @@ Player::PlayNextChunk()
}
inline void
-Player::SongBorder()
+Player::SongBorder() noexcept
{
- FormatDefault(player_domain, "played \"%s\"", song->GetURI());
+ {
+ const ScopeUnlock unlock(pc.mutex);
- throttle_silence_log.Reset();
+ FormatDefault(player_domain, "played \"%s\"", song->GetURI());
- ReplacePipe(dc.pipe);
+ ReplacePipe(dc.pipe);
- pc.outputs.SongBorder();
+ pc.outputs.SongBorder();
+ }
ActivateDecoder();
- const bool border_pause = pc.LockApplyBorderPause();
+ const bool border_pause = pc.ApplyBorderPause();
if (border_pause) {
paused = true;
+ pc.listener.OnBorderPause();
idle_add(IDLE_PLAYER);
}
}
inline void
-Player::Run()
+Player::Run() noexcept
{
pipe = new MusicPipe();
+ const std::lock_guard<Mutex> lock(pc.mutex);
+
StartDecoder(*pipe);
ActivateDecoder();
- pc.Lock();
pc.state = PlayerState::PLAY;
pc.CommandFinished();
while (true) {
- ProcessCommand();
- if (pc.command == PlayerCommand::STOP ||
- pc.command == PlayerCommand::EXIT ||
- pc.command == PlayerCommand::CLOSE_AUDIO) {
- pc.Unlock();
- pc.outputs.Cancel();
+ if (!ProcessCommand())
break;
- }
-
- pc.Unlock();
if (buffering) {
/* buffering at the start of the song - wait
@@ -985,16 +959,9 @@ Player::Run()
prevent stuttering on slow machines */
if (pipe->GetSize() < pc.buffered_before_play &&
- !dc.LockIsIdle()) {
+ !dc.IsIdle()) {
/* not enough decoded buffer space yet */
- if (!paused && output_open &&
- pc.outputs.Check() < 4 &&
- !SendSilence())
- break;
-
- pc.Lock();
- /* XXX race condition: check decoder again */
dc.WaitForDecoder();
continue;
} else {
@@ -1006,25 +973,13 @@ Player::Run()
if (decoder_starting) {
/* wait until the decoder is initialized completely */
- pc.Lock();
-
- if (!CheckDecoderStartup()) {
- pc.Unlock();
+ if (!CheckDecoderStartup())
break;
- }
continue;
}
-#ifndef NDEBUG
- /*
- music_pipe_check_format(&play_audio_format,
- next_song_chunk,
- &dc.out_audio_format);
- */
-#endif
-
- if (dc.LockIsIdle() && queued && dc.pipe == pipe) {
+ if (dc.IsIdle() && queued && dc.pipe == pipe) {
/* the decoder has finished the current song;
make it decode the next song */
@@ -1038,7 +993,7 @@ Player::Run()
!pc.border_pause &&
IsDecoderAtNextSong() &&
xfade_state == CrossFadeState::UNKNOWN &&
- !dc.LockIsStarting()) {
+ !dc.IsStarting()) {
/* enable cross fading in this song? if yes,
calculate how many chunks will be required
for it */
@@ -1061,94 +1016,88 @@ Player::Run()
}
if (paused) {
- pc.Lock();
-
if (pc.command == PlayerCommand::NONE)
pc.Wait();
- continue;
} else if (!pipe->IsEmpty()) {
/* at least one music chunk is ready - send it
to the audio output */
+ const ScopeUnlock unlock(pc.mutex);
PlayNextChunk();
- } else if (pc.outputs.Check() > 0) {
+ } else if (UnlockCheckOutputs() > 0) {
/* not enough data from decoder, but the
output thread is still busy, so it's
okay */
- pc.Lock();
-
/* wake up the decoder (just in case it's
waiting for space in the MusicBuffer) and
wait for it */
+ // TODO: eliminate this kludge
dc.Signal();
+
dc.WaitForDecoder();
- continue;
} else if (IsDecoderAtNextSong()) {
/* at the beginning of a new song */
SongBorder();
- } else if (dc.LockIsIdle()) {
+ } else if (dc.IsIdle()) {
/* check the size of the pipe again, because
the decoder thread may have added something
since we last checked */
if (pipe->IsEmpty()) {
/* wait for the hardware to finish
playback */
+ const ScopeUnlock unlock(pc.mutex);
pc.outputs.Drain();
break;
}
} else if (output_open) {
/* the decoder is too busy and hasn't provided
- new PCM data in time: send silence (if the
- output pipe is empty) */
+ new PCM data in time: wait for the
+ decoder */
- if (throttle_silence_log.CheckUpdate(std::chrono::seconds(5)))
- FormatWarning(player_domain, "Decoder is too slow; playing silence to avoid xrun");
+ /* wake up the decoder (just in case it's
+ waiting for space in the MusicBuffer) and
+ wait for it */
+ // TODO: eliminate this kludge
+ dc.Signal();
- if (!SendSilence())
- break;
+ dc.WaitForDecoder();
}
-
- pc.Lock();
}
+ CancelPendingSeek();
StopDecoder();
ClearAndDeletePipe();
- delete cross_fade_tag;
+ cross_fade_tag.reset();
if (song != nullptr) {
FormatDefault(player_domain, "played \"%s\"", song->GetURI());
- delete song;
+ song.reset();
}
- pc.Lock();
-
pc.ClearTaggedSong();
if (queued) {
assert(pc.next_song != nullptr);
- delete pc.next_song;
- pc.next_song = nullptr;
+ pc.next_song.reset();
}
pc.state = PlayerState::STOP;
-
- pc.Unlock();
}
static void
do_play(PlayerControl &pc, DecoderControl &dc,
- MusicBuffer &buffer)
+ MusicBuffer &buffer) noexcept
{
Player player(pc, dc, buffer);
player.Run();
}
void
-PlayerControl::RunThread()
+PlayerControl::RunThread() noexcept
{
SetThreadName("player");
@@ -1159,7 +1108,7 @@ PlayerControl::RunThread()
MusicBuffer buffer(buffer_chunks);
- Lock();
+ const std::lock_guard<Mutex> lock(mutex);
while (1) {
switch (command) {
@@ -1167,32 +1116,34 @@ PlayerControl::RunThread()
case PlayerCommand::QUEUE:
assert(next_song != nullptr);
- Unlock();
- do_play(*this, dc, buffer);
- listener.OnPlayerSync();
- Lock();
+ {
+ const ScopeUnlock unlock(mutex);
+ do_play(*this, dc, buffer);
+ listener.OnPlayerSync();
+ }
+
break;
case PlayerCommand::STOP:
- Unlock();
- outputs.Cancel();
- Lock();
+ {
+ const ScopeUnlock unlock(mutex);
+ outputs.Cancel();
+ }
/* fall through */
case PlayerCommand::PAUSE:
- delete next_song;
- next_song = nullptr;
+ next_song.reset();
CommandFinished();
break;
case PlayerCommand::CLOSE_AUDIO:
- Unlock();
-
- outputs.Release();
+ {
+ const ScopeUnlock unlock(mutex);
+ outputs.Release();
+ }
- Lock();
CommandFinished();
assert(buffer.IsEmptyUnsafe());
@@ -1200,25 +1151,26 @@ PlayerControl::RunThread()
break;
case PlayerCommand::UPDATE_AUDIO:
- Unlock();
- outputs.EnableDisable();
- Lock();
+ {
+ const ScopeUnlock unlock(mutex);
+ outputs.EnableDisable();
+ }
+
CommandFinished();
break;
case PlayerCommand::EXIT:
- Unlock();
-
- dc.Quit();
-
- outputs.Close();
+ {
+ const ScopeUnlock unlock(mutex);
+ dc.Quit();
+ outputs.Close();
+ }
- LockCommandFinished();
+ CommandFinished();
return;
case PlayerCommand::CANCEL:
- delete next_song;
- next_song = nullptr;
+ next_song.reset();
CommandFinished();
break;