aboutsummaryrefslogtreecommitdiffstats
path: root/src/PlayerThread.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/PlayerThread.cxx')
-rw-r--r--src/PlayerThread.cxx1201
1 files changed, 1201 insertions, 0 deletions
diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx
new file mode 100644
index 000000000..67cfc1498
--- /dev/null
+++ b/src/PlayerThread.cxx
@@ -0,0 +1,1201 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlayerThread.hxx"
+#include "DecoderThread.hxx"
+#include "DecoderControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "Song.hxx"
+#include "Main.hxx"
+#include "system/FatalError.hxx"
+#include "CrossFade.hxx"
+#include "PlayerControl.hxx"
+#include "OutputAll.hxx"
+#include "tag/Tag.hxx"
+#include "Idle.hxx"
+#include "GlobalEvents.hxx"
+
+#include <cmath>
+
+#include <glib.h>
+
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "player_thread"
+
+enum class CrossFadeState : int8_t {
+ DISABLED = -1,
+ UNKNOWN = 0,
+ ENABLED = 1
+};
+
+class Player {
+ player_control &pc;
+
+ decoder_control &dc;
+
+ MusicBuffer &buffer;
+
+ MusicPipe *pipe;
+
+ /**
+ * are we waiting for buffered_before_play?
+ */
+ bool buffering;
+
+ /**
+ * true if the decoder is starting and did not provide data
+ * yet
+ */
+ bool decoder_starting;
+
+ /**
+ * is the player paused?
+ */
+ bool paused;
+
+ /**
+ * is there a new song in pc.next_song?
+ */
+ bool queued;
+
+ /**
+ * Was any audio output opened successfully? It might have
+ * failed meanwhile, but was not explicitly closed by the
+ * player thread. When this flag is unset, some output
+ * methods must not be called.
+ */
+ bool output_open;
+
+ /**
+ * the song currently being played
+ */
+ Song *song;
+
+ /**
+ * is cross fading enabled?
+ */
+ CrossFadeState xfade_state;
+
+ /**
+ * has cross-fading begun?
+ */
+ bool cross_fading;
+
+ /**
+ * 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;
+
+ /**
+ * The current audio format for the audio outputs.
+ */
+ AudioFormat play_audio_format;
+
+ /**
+ * The time stamp of the chunk most recently sent to the
+ * output thread. This attribute is only used if
+ * audio_output_all_get_elapsed_time() didn't return a usable
+ * value; the output thread can estimate the elapsed time more
+ * precisely.
+ */
+ float elapsed_time;
+
+public:
+ Player(player_control &_pc, decoder_control &_dc,
+ MusicBuffer &_buffer)
+ :pc(_pc), dc(_dc), buffer(_buffer),
+ buffering(false),
+ decoder_starting(false),
+ paused(false),
+ queued(true),
+ output_open(false),
+ song(nullptr),
+ xfade_state(CrossFadeState::UNKNOWN),
+ cross_fading(false),
+ cross_fade_chunks(0),
+ cross_fade_tag(nullptr),
+ elapsed_time(0.0) {}
+
+private:
+ void ClearAndDeletePipe() {
+ pipe->Clear(buffer);
+ delete pipe;
+ }
+
+ void ClearAndReplacePipe(MusicPipe *_pipe) {
+ ClearAndDeletePipe();
+ pipe = _pipe;
+ }
+
+ void ReplacePipe(MusicPipe *_pipe) {
+ delete pipe;
+ pipe = _pipe;
+ }
+
+ /**
+ * Start the decoder.
+ *
+ * Player lock is not held.
+ */
+ void StartDecoder(MusicPipe &pipe);
+
+ /**
+ * The decoder has acknowledged the "START" command (see
+ * player::WaitForDecoder()). This function checks if the decoder
+ * initialization has completed yet.
+ *
+ * The player lock is not held.
+ */
+ bool CheckDecoderStartup();
+
+ /**
+ * Stop the decoder and clears (and frees) its music pipe.
+ *
+ * Player lock is not held.
+ */
+ void StopDecoder();
+
+ /**
+ * Is the decoder still busy on the same song as the player?
+ *
+ * Note: this function does not check if the decoder is already
+ * finished.
+ */
+ gcc_pure
+ bool IsDecoderAtCurrentSong() const {
+ assert(pipe != nullptr);
+
+ return dc.pipe == pipe;
+ }
+
+ /**
+ * Returns true if the decoder is decoding the next song (or has begun
+ * decoding it, or has finished doing it), and the player hasn't
+ * switched to that song yet.
+ */
+ gcc_pure
+ bool IsDecoderAtNextSong() const {
+ return dc.pipe != nullptr && !IsDecoderAtCurrentSong();
+ }
+
+ /**
+ * This is the handler for the #PlayerCommand::SEEK command.
+ *
+ * The player lock is not held.
+ */
+ bool SeekDecoder();
+
+ /**
+ * After the decoder has been started asynchronously, wait for
+ * the "START" command to finish. The decoder may not be
+ * initialized yet, i.e. there is no audio_format information
+ * yet.
+ *
+ * The player lock is not held.
+ */
+ bool WaitForDecoder();
+
+ /**
+ * Wrapper for audio_output_all_open(). Upon failure, it pauses the
+ * player.
+ *
+ * @return true on success
+ */
+ bool OpenOutput();
+
+ /**
+ * Obtains the next chunk from the music pipe, optionally applies
+ * cross-fading, and sends it to all audio outputs.
+ *
+ * @return true on success, false on error (playback will be stopped)
+ */
+ bool PlayNextChunk();
+
+ /**
+ * 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();
+
+ /**
+ * Player lock must be held before calling.
+ */
+ void ProcessCommand();
+
+ /**
+ * 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.
+ *
+ * @return true on success, false on error (playback will be stopped)
+ */
+ bool SongBorder();
+
+public:
+ /*
+ * The main loop of the player thread, during playback. This
+ * is basically a state machine, which multiplexes data
+ * between the decoder thread and the output threads.
+ */
+ void Run();
+};
+
+static void
+player_command_finished(player_control &pc)
+{
+ pc.Lock();
+ pc.CommandFinished();
+ pc.Unlock();
+}
+
+void
+Player::StartDecoder(MusicPipe &_pipe)
+{
+ assert(queued || pc.command == PlayerCommand::SEEK);
+ assert(pc.next_song != nullptr);
+
+ unsigned start_ms = pc.next_song->start_ms;
+ if (pc.command == PlayerCommand::SEEK)
+ start_ms += (unsigned)(pc.seek_where * 1000);
+
+ dc.Start(pc.next_song->DupDetached(),
+ start_ms, pc.next_song->end_ms,
+ buffer, _pipe);
+}
+
+void
+Player::StopDecoder()
+{
+ dc.Stop();
+
+ if (dc.pipe != nullptr) {
+ /* clear and free the decoder pipe */
+
+ dc.pipe->Clear(buffer);
+
+ if (dc.pipe != pipe)
+ delete dc.pipe;
+
+ dc.pipe = nullptr;
+ }
+}
+
+bool
+Player::WaitForDecoder()
+{
+ assert(queued || pc.command == PlayerCommand::SEEK);
+ assert(pc.next_song != nullptr);
+
+ queued = false;
+
+ Error error = dc.LockGetError();
+ if (error.IsDefined()) {
+ pc.Lock();
+ pc.SetError(PlayerError::DECODER, std::move(error));
+
+ pc.next_song->Free();
+ pc.next_song = nullptr;
+
+ pc.Unlock();
+
+ return false;
+ }
+
+ if (song != nullptr)
+ song->Free();
+
+ song = pc.next_song;
+ elapsed_time = 0.0;
+
+ /* set the "starting" flag, which will be cleared by
+ player_check_decoder_startup() */
+ decoder_starting = true;
+
+ pc.Lock();
+
+ /* update player_control's song information */
+ pc.total_time = pc.next_song->GetDuration();
+ pc.bit_rate = 0;
+ pc.audio_format.Clear();
+
+ /* clear the queued song */
+ pc.next_song = nullptr;
+
+ pc.Unlock();
+
+ /* call syncPlaylistWithQueue() in the main thread */
+ GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+
+ return true;
+}
+
+/**
+ * Returns the real duration of the song, comprising the duration
+ * indicated by the decoder plugin.
+ */
+static double
+real_song_duration(const Song *song, double decoder_duration)
+{
+ assert(song != nullptr);
+
+ if (decoder_duration <= 0.0)
+ /* the decoder plugin didn't provide information; fall
+ back to Song::GetDuration() */
+ return song->GetDuration();
+
+ if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration)
+ return (song->end_ms - song->start_ms) / 1000.0;
+
+ return decoder_duration - song->start_ms / 1000.0;
+}
+
+bool
+Player::OpenOutput()
+{
+ assert(play_audio_format.IsDefined());
+ assert(pc.state == PlayerState::PLAY ||
+ pc.state == PlayerState::PAUSE);
+
+ Error error;
+ if (audio_output_all_open(play_audio_format, buffer, error)) {
+ output_open = true;
+ paused = false;
+
+ pc.Lock();
+ pc.state = PlayerState::PLAY;
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return true;
+ } else {
+ g_warning("%s", error.GetMessage());
+
+ output_open = false;
+
+ /* pause: the user may resume playback as soon as an
+ audio output becomes available */
+ paused = true;
+
+ pc.Lock();
+ pc.SetError(PlayerError::OUTPUT, std::move(error));
+ pc.state = PlayerState::PAUSE;
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return false;
+ }
+}
+
+bool
+Player::CheckDecoderStartup()
+{
+ assert(decoder_starting);
+
+ dc.Lock();
+
+ Error error = dc.GetError();
+ if (error.IsDefined()) {
+ /* the decoder failed */
+ dc.Unlock();
+
+ pc.Lock();
+ pc.SetError(PlayerError::DECODER, std::move(error));
+ pc.Unlock();
+
+ return false;
+ } else if (!dc.IsStarting()) {
+ /* the decoder is ready and ok */
+
+ dc.Unlock();
+
+ if (output_open &&
+ !audio_output_all_wait(&pc, 1))
+ /* the output devices havn't finished playing
+ all chunks yet - wait for that */
+ return true;
+
+ pc.Lock();
+ pc.total_time = real_song_duration(dc.song, dc.total_time);
+ pc.audio_format = dc.in_audio_format;
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ play_audio_format = dc.out_audio_format;
+ decoder_starting = false;
+
+ if (!paused && !OpenOutput()) {
+ char *uri = dc.song->GetURI();
+ g_warning("problems opening audio device "
+ "while playing \"%s\"", uri);
+ g_free(uri);
+
+ return true;
+ }
+
+ return true;
+ } else {
+ /* the decoder is not yet ready; wait
+ some more */
+ dc.WaitForDecoder();
+ dc.Unlock();
+
+ return true;
+ }
+}
+
+bool
+Player::SendSilence()
+{
+ assert(output_open);
+ assert(play_audio_format.IsDefined());
+
+ struct music_chunk *chunk = buffer.Allocate();
+ if (chunk == nullptr) {
+ g_warning("Failed to allocate silence buffer");
+ return false;
+ }
+
+#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->times = -1.0; /* undefined time stamp */
+ chunk->length = num_frames * frame_size;
+ memset(chunk->data, 0, chunk->length);
+
+ Error error;
+ if (!audio_output_all_play(chunk, error)) {
+ g_warning("%s", error.GetMessage());
+ buffer.Return(chunk);
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+Player::SeekDecoder()
+{
+ assert(pc.next_song != nullptr);
+
+ const unsigned start_ms = pc.next_song->start_ms;
+
+ if (!dc.LockIsCurrentSong(pc.next_song)) {
+ /* the decoder is already decoding the "next" song -
+ stop it and start the previous song again */
+
+ StopDecoder();
+
+ /* clear music chunks which might still reside in the
+ pipe */
+ pipe->Clear(buffer);
+
+ /* re-start the decoder */
+ StartDecoder(*pipe);
+ if (!WaitForDecoder()) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+ } else {
+ if (!IsDecoderAtCurrentSong()) {
+ /* the decoder is already decoding the "next" song,
+ but it is the same song file; exchange the pipe */
+ ClearAndReplacePipe(dc.pipe);
+ }
+
+ pc.next_song->Free();
+ pc.next_song = nullptr;
+ queued = false;
+ }
+
+ /* wait for the decoder to complete initialization */
+
+ while (decoder_starting) {
+ if (!CheckDecoderStartup()) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+ }
+
+ /* send the SEEK command */
+
+ double where = pc.seek_where;
+ if (where > pc.total_time)
+ where = pc.total_time - 0.1;
+ if (where < 0.0)
+ where = 0.0;
+
+ if (!dc.Seek(where + start_ms / 1000.0)) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+
+ elapsed_time = where;
+
+ player_command_finished(pc);
+
+ xfade_state = CrossFadeState::UNKNOWN;
+
+ /* re-fill the buffer after seeking */
+ buffering = true;
+
+ audio_output_all_cancel();
+
+ return true;
+}
+
+inline void
+Player::ProcessCommand()
+{
+ switch (pc.command) {
+ case PlayerCommand::NONE:
+ case PlayerCommand::STOP:
+ case PlayerCommand::EXIT:
+ case PlayerCommand::CLOSE_AUDIO:
+ break;
+
+ case PlayerCommand::UPDATE_AUDIO:
+ pc.Unlock();
+ audio_output_all_enable_disable();
+ pc.Lock();
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::QUEUE:
+ assert(pc.next_song != nullptr);
+ assert(!queued);
+ assert(!IsDecoderAtNextSong());
+
+ queued = true;
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::PAUSE:
+ pc.Unlock();
+
+ paused = !paused;
+ if (paused) {
+ audio_output_all_pause();
+ pc.Lock();
+
+ pc.state = PlayerState::PAUSE;
+ } else if (!play_audio_format.IsDefined()) {
+ /* the decoder hasn't provided an audio format
+ yet - don't open the audio device yet */
+ pc.Lock();
+
+ pc.state = PlayerState::PLAY;
+ } else {
+ OpenOutput();
+
+ pc.Lock();
+ }
+
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::SEEK:
+ pc.Unlock();
+ SeekDecoder();
+ pc.Lock();
+ break;
+
+ case PlayerCommand::CANCEL:
+ 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;
+ }
+
+ if (IsDecoderAtNextSong()) {
+ /* the decoder is already decoding the song -
+ stop it and reset the position */
+ pc.Unlock();
+ StopDecoder();
+ pc.Lock();
+ }
+
+ pc.next_song->Free();
+ pc.next_song = nullptr;
+ queued = false;
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::REFRESH:
+ if (output_open && !paused) {
+ pc.Unlock();
+ audio_output_all_check();
+ pc.Lock();
+ }
+
+ pc.elapsed_time = audio_output_all_get_elapsed_time();
+ if (pc.elapsed_time < 0.0)
+ pc.elapsed_time = elapsed_time;
+
+ pc.CommandFinished();
+ break;
+ }
+}
+
+static void
+update_song_tag(Song *song, const Tag &new_tag)
+{
+ if (song->IsFile())
+ /* don't update tags of local files, only remote
+ streams may change tags dynamically */
+ return;
+
+ Tag *old_tag = song->tag;
+ song->tag = new Tag(new_tag);
+
+ delete old_tag;
+
+ /* the main thread will update the playlist version when he
+ receives this event */
+ GlobalEvents::Emit(GlobalEvents::TAG);
+
+ /* notify all clients that the tag of the current song has
+ changed */
+ idle_add(IDLE_PLAYER);
+}
+
+/**
+ * Plays a #music_chunk object (after applying software volume). If
+ * it contains a (stream) tag, copy it to the current song, so MPD's
+ * playlist reflects the new stream tag.
+ *
+ * Player lock is not held.
+ */
+static bool
+play_chunk(player_control &pc,
+ Song *song, struct music_chunk *chunk,
+ MusicBuffer &buffer,
+ const AudioFormat format,
+ Error &error)
+{
+ assert(chunk->CheckFormat(format));
+
+ if (chunk->tag != nullptr)
+ update_song_tag(song, *chunk->tag);
+
+ if (chunk->length == 0) {
+ buffer.Return(chunk);
+ return true;
+ }
+
+ pc.Lock();
+ pc.bit_rate = chunk->bit_rate;
+ pc.Unlock();
+
+ /* send the chunk to the audio outputs */
+
+ if (!audio_output_all_play(chunk, error))
+ return false;
+
+ pc.total_play_time += (double)chunk->length /
+ format.GetTimeToSize();
+ return true;
+}
+
+inline bool
+Player::PlayNextChunk()
+{
+ if (!audio_output_all_wait(&pc, 64))
+ /* the output pipe is still large enough, don't send
+ another chunk */
+ return true;
+
+ unsigned cross_fade_position;
+ struct music_chunk *chunk = nullptr;
+ if (xfade_state == CrossFadeState::ENABLED && IsDecoderAtNextSong() &&
+ (cross_fade_position = pipe->GetSize()) <= cross_fade_chunks) {
+ /* perform cross fade */
+ music_chunk *other_chunk = dc.pipe->Shift();
+
+ if (!cross_fading) {
+ /* beginning of the cross fade - adjust
+ crossFadeChunks which might be bigger than
+ the remaining number of chunks in the old
+ song */
+ cross_fade_chunks = cross_fade_position;
+ cross_fading = true;
+ }
+
+ if (other_chunk != nullptr) {
+ chunk = pipe->Shift();
+ assert(chunk != nullptr);
+ assert(chunk->other == nullptr);
+
+ /* 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;
+
+ if (std::isnan(pc.mixramp_delay_seconds)) {
+ chunk->mix_ratio = ((float)cross_fade_position)
+ / cross_fade_chunks;
+ } else {
+ chunk->mix_ratio = nan("");
+ }
+
+ if (other_chunk->IsEmpty()) {
+ /* the "other" chunk was a music_chunk
+ which had only a tag, but no music
+ data - we cannot cross-fade that;
+ but since this happens only at the
+ beginning of the new song, we can
+ easily recover by throwing it away
+ now */
+ buffer.Return(other_chunk);
+ other_chunk = nullptr;
+ }
+
+ chunk->other = other_chunk;
+ } else {
+ /* there are not enough decoded chunks yet */
+
+ dc.Lock();
+
+ if (dc.IsIdle()) {
+ /* the decoder isn't running, abort
+ cross fading */
+ dc.Unlock();
+
+ xfade_state = CrossFadeState::DISABLED;
+ } else {
+ /* wait for the decoder */
+ dc.Signal();
+ dc.WaitForDecoder();
+ dc.Unlock();
+
+ return true;
+ }
+ }
+ }
+
+ if (chunk == nullptr)
+ chunk = pipe->Shift();
+
+ assert(chunk != nullptr);
+
+ /* insert the postponed tag if cross-fading is finished */
+
+ if (xfade_state != CrossFadeState::ENABLED && cross_fade_tag != nullptr) {
+ chunk->tag = Tag::MergeReplace(chunk->tag, cross_fade_tag);
+ cross_fade_tag = nullptr;
+ }
+
+ /* play the current chunk */
+
+ Error error;
+ if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) {
+ g_warning("%s", error.GetMessage());
+
+ buffer.Return(chunk);
+
+ pc.Lock();
+
+ pc.SetError(PlayerError::OUTPUT, std::move(error));
+
+ /* pause: the user may resume playback as soon as an
+ audio output becomes available */
+ pc.state = PlayerState::PAUSE;
+ paused = true;
+
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return false;
+ }
+
+ /* this formula should prevent that the decoder gets woken up
+ with each chunk; it is more efficient to make it decode a
+ larger block at a time */
+ dc.Lock();
+ if (!dc.IsIdle() &&
+ dc.pipe->GetSize() <= (pc.buffered_before_play +
+ buffer.GetSize() * 3) / 4)
+ dc.Signal();
+ dc.Unlock();
+
+ return true;
+}
+
+inline bool
+Player::SongBorder()
+{
+ xfade_state = CrossFadeState::UNKNOWN;
+
+ char *uri = song->GetURI();
+ g_message("played \"%s\"", uri);
+ g_free(uri);
+
+ ReplacePipe(dc.pipe);
+
+ audio_output_all_song_border();
+
+ if (!WaitForDecoder())
+ return false;
+
+ pc.Lock();
+
+ const bool border_pause = pc.border_pause;
+ if (border_pause) {
+ paused = true;
+ pc.state = PlayerState::PAUSE;
+ }
+
+ pc.Unlock();
+
+ if (border_pause)
+ idle_add(IDLE_PLAYER);
+
+ return true;
+}
+
+inline void
+Player::Run()
+{
+ pipe = new MusicPipe();
+
+ StartDecoder(*pipe);
+ if (!WaitForDecoder()) {
+ assert(song == nullptr);
+
+ StopDecoder();
+ player_command_finished(pc);
+ delete pipe;
+ return;
+ }
+
+ pc.Lock();
+ pc.state = PlayerState::PLAY;
+
+ if (pc.command == PlayerCommand::SEEK)
+ elapsed_time = pc.seek_where;
+
+ pc.CommandFinished();
+
+ while (true) {
+ ProcessCommand();
+ if (pc.command == PlayerCommand::STOP ||
+ pc.command == PlayerCommand::EXIT ||
+ pc.command == PlayerCommand::CLOSE_AUDIO) {
+ pc.Unlock();
+ audio_output_all_cancel();
+ break;
+ }
+
+ pc.Unlock();
+
+ if (buffering) {
+ /* buffering at the start of the song - wait
+ until the buffer is large enough, to
+ prevent stuttering on slow machines */
+
+ if (pipe->GetSize() < pc.buffered_before_play &&
+ !dc.LockIsIdle()) {
+ /* not enough decoded buffer space yet */
+
+ if (!paused && output_open &&
+ audio_output_all_check() < 4 &&
+ !SendSilence())
+ break;
+
+ dc.Lock();
+ /* XXX race condition: check decoder again */
+ dc.WaitForDecoder();
+ dc.Unlock();
+ pc.Lock();
+ continue;
+ } else {
+ /* buffering is complete */
+ buffering = false;
+ }
+ }
+
+ if (decoder_starting) {
+ /* wait until the decoder is initialized completely */
+
+ if (!CheckDecoderStartup())
+ break;
+
+ pc.Lock();
+ 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) {
+ /* the decoder has finished the current song;
+ make it decode the next song */
+
+ assert(dc.pipe == nullptr || dc.pipe == pipe);
+
+ StartDecoder(*new MusicPipe());
+ }
+
+ if (/* no cross-fading if MPD is going to pause at the
+ end of the current song */
+ !pc.border_pause &&
+ IsDecoderAtNextSong() &&
+ xfade_state == CrossFadeState::UNKNOWN &&
+ !dc.LockIsStarting()) {
+ /* enable cross fading in this song? if yes,
+ calculate how many chunks will be required
+ for it */
+ cross_fade_chunks =
+ cross_fade_calc(pc.cross_fade_seconds, dc.total_time,
+ pc.mixramp_db,
+ pc.mixramp_delay_seconds,
+ dc.replay_gain_db,
+ dc.replay_gain_prev_db,
+ dc.mixramp_start,
+ dc.mixramp_prev_end,
+ dc.out_audio_format,
+ play_audio_format,
+ buffer.GetSize() -
+ pc.buffered_before_play);
+ if (cross_fade_chunks > 0) {
+ xfade_state = CrossFadeState::ENABLED;
+ cross_fading = false;
+ } else
+ /* cross fading is disabled or the
+ next song is too short */
+ xfade_state = CrossFadeState::DISABLED;
+ }
+
+ 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 */
+
+ PlayNextChunk();
+ } else if (audio_output_all_check() > 0) {
+ /* not enough data from decoder, but the
+ output thread is still busy, so it's
+ okay */
+
+ /* XXX synchronize in a better way */
+ g_usleep(10000);
+ } else if (IsDecoderAtNextSong()) {
+ /* at the beginning of a new song */
+
+ if (!SongBorder())
+ break;
+ } else if (dc.LockIsIdle()) {
+ /* 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 */
+ audio_output_all_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) */
+ if (!SendSilence())
+ break;
+ }
+
+ pc.Lock();
+ }
+
+ StopDecoder();
+
+ ClearAndDeletePipe();
+
+ delete cross_fade_tag;
+
+ if (song != nullptr)
+ song->Free();
+
+ pc.Lock();
+
+ if (queued) {
+ assert(pc.next_song != nullptr);
+ pc.next_song->Free();
+ pc.next_song = nullptr;
+ }
+
+ pc.state = PlayerState::STOP;
+
+ pc.Unlock();
+}
+
+static void
+do_play(player_control &pc, decoder_control &dc,
+ MusicBuffer &buffer)
+{
+ Player player(pc, dc, buffer);
+ player.Run();
+}
+
+static gpointer
+player_task(gpointer arg)
+{
+ player_control &pc = *(player_control *)arg;
+
+ decoder_control dc;
+ decoder_thread_start(&dc);
+
+ MusicBuffer buffer(pc.buffer_chunks);
+
+ pc.Lock();
+
+ while (1) {
+ switch (pc.command) {
+ case PlayerCommand::SEEK:
+ case PlayerCommand::QUEUE:
+ assert(pc.next_song != nullptr);
+
+ pc.Unlock();
+ do_play(pc, dc, buffer);
+ GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+ pc.Lock();
+ break;
+
+ case PlayerCommand::STOP:
+ pc.Unlock();
+ audio_output_all_cancel();
+ pc.Lock();
+
+ /* fall through */
+
+ case PlayerCommand::PAUSE:
+ if (pc.next_song != nullptr) {
+ pc.next_song->Free();
+ pc.next_song = nullptr;
+ }
+
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::CLOSE_AUDIO:
+ pc.Unlock();
+
+ audio_output_all_release();
+
+ pc.Lock();
+ pc.CommandFinished();
+
+ assert(buffer.IsEmptyUnsafe());
+
+ break;
+
+ case PlayerCommand::UPDATE_AUDIO:
+ pc.Unlock();
+ audio_output_all_enable_disable();
+ pc.Lock();
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::EXIT:
+ pc.Unlock();
+
+ dc.Quit();
+
+ audio_output_all_close();
+
+ player_command_finished(pc);
+ return nullptr;
+
+ case PlayerCommand::CANCEL:
+ if (pc.next_song != nullptr) {
+ pc.next_song->Free();
+ pc.next_song = nullptr;
+ }
+
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::REFRESH:
+ /* no-op when not playing */
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::NONE:
+ pc.Wait();
+ break;
+ }
+ }
+}
+
+void
+player_create(player_control &pc)
+{
+ assert(pc.thread == nullptr);
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ pc.thread = g_thread_new("player", player_task, &pc);
+#else
+ GError *e = nullptr;
+ pc.thread = g_thread_create(player_task, &pc, true, &e);
+ if (pc.thread == nullptr)
+ FatalError("Failed to spawn player task", e);
+#endif
+}