diff options
Diffstat (limited to 'src/player')
-rw-r--r-- | src/player/Control.cxx | 102 | ||||
-rw-r--r-- | src/player/Control.hxx | 216 | ||||
-rw-r--r-- | src/player/CrossFade.cxx | 2 | ||||
-rw-r--r-- | src/player/Listener.hxx | 9 | ||||
-rw-r--r-- | src/player/Outputs.hxx | 117 | ||||
-rw-r--r-- | src/player/Thread.cxx | 580 |
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; |