player_control: protect command, state, error with a mutex

Use GMutex/GCond instead of the notify library.  Manually lock the
player_control object before accessing the protected attributes.  Use
the GCond object to notify the player thread and the main thread.
This commit is contained in:
Max Kellermann 2009-10-31 17:02:12 +01:00
parent 73cff374fd
commit 25a806a347
12 changed files with 264 additions and 90 deletions

View File

@ -61,7 +61,7 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
dc.state = DECODE_STATE_DECODE; dc.state = DECODE_STATE_DECODE;
decoder_unlock(); decoder_unlock();
notify_signal(&pc.notify); player_lock_signal();
g_debug("audio_format=%u:%u:%u, seekable=%s", g_debug("audio_format=%u:%u:%u, seekable=%s",
dc.in_audio_format.sample_rate, dc.in_audio_format.bits, dc.in_audio_format.sample_rate, dc.in_audio_format.bits,
@ -112,7 +112,7 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
dc.command = DECODE_COMMAND_NONE; dc.command = DECODE_COMMAND_NONE;
decoder_unlock(); decoder_unlock();
notify_signal(&pc.notify); player_lock_signal();
} }
double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
@ -184,7 +184,7 @@ do_send_tag(struct decoder *decoder, struct input_stream *is,
/* there is a partial chunk - flush it, we want the /* there is a partial chunk - flush it, we want the
tag in a new chunk */ tag in a new chunk */
decoder_flush_chunk(decoder); decoder_flush_chunk(decoder);
notify_signal(&pc.notify); player_lock_signal();
} }
assert(decoder->chunk == NULL); assert(decoder->chunk == NULL);
@ -297,7 +297,7 @@ decoder_data(struct decoder *decoder,
if (dest == NULL) { if (dest == NULL) {
/* the chunk is full, flush it */ /* the chunk is full, flush it */
decoder_flush_chunk(decoder); decoder_flush_chunk(decoder);
notify_signal(&pc.notify); player_lock_signal();
continue; continue;
} }
@ -324,7 +324,7 @@ decoder_data(struct decoder *decoder,
if (full) { if (full) {
/* the chunk is full, flush it */ /* the chunk is full, flush it */
decoder_flush_chunk(decoder); decoder_flush_chunk(decoder);
notify_signal(&pc.notify); player_lock_signal();
} }
data += nbytes; data += nbytes;

View File

@ -18,7 +18,7 @@
*/ */
#include "decoder_control.h" #include "decoder_control.h"
#include "notify.h" #include "player_control.h"
#include <assert.h> #include <assert.h>
@ -40,38 +40,34 @@ void dc_deinit(void)
} }
static void static void
dc_command_wait_locked(struct notify *notify) dc_command_wait_locked(void)
{ {
while (dc.command != DECODE_COMMAND_NONE) { while (dc.command != DECODE_COMMAND_NONE) {
decoder_signal(); decoder_signal();
decoder_unlock(); player_wait_decoder();
notify_wait(notify);
decoder_lock();
} }
} }
void void
dc_command_wait(struct notify *notify) dc_command_wait(void)
{ {
decoder_lock(); decoder_lock();
dc_command_wait_locked(notify); dc_command_wait_locked();
decoder_unlock(); decoder_unlock();
} }
static void static void
dc_command_locked(struct notify *notify, enum decoder_command cmd) dc_command_locked(enum decoder_command cmd)
{ {
dc.command = cmd; dc.command = cmd;
dc_command_wait_locked(notify); dc_command_wait_locked();
} }
static void static void
dc_command(struct notify *notify, enum decoder_command cmd) dc_command(enum decoder_command cmd)
{ {
decoder_lock(); decoder_lock();
dc_command_locked(notify, cmd); dc_command_locked(cmd);
decoder_unlock(); decoder_unlock();
} }
@ -86,13 +82,13 @@ static void dc_command_async(enum decoder_command cmd)
} }
void void
dc_start(struct notify *notify, struct song *song) dc_start(struct song *song)
{ {
assert(dc.pipe != NULL); assert(dc.pipe != NULL);
assert(song != NULL); assert(song != NULL);
dc.next_song = song; dc.next_song = song;
dc_command(notify, DECODE_COMMAND_START); dc_command(DECODE_COMMAND_START);
} }
void void
@ -106,7 +102,7 @@ dc_start_async(struct song *song)
} }
void void
dc_stop(struct notify *notify) dc_stop(void)
{ {
decoder_lock(); decoder_lock();
@ -115,16 +111,16 @@ dc_stop(struct notify *notify)
late and the decoder thread is already executing late and the decoder thread is already executing
the old command, we'll call STOP again in this the old command, we'll call STOP again in this
function (see below). */ function (see below). */
dc_command_locked(notify, DECODE_COMMAND_STOP); dc_command_locked(DECODE_COMMAND_STOP);
if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR) if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR)
dc_command_locked(notify, DECODE_COMMAND_STOP); dc_command_locked(DECODE_COMMAND_STOP);
decoder_unlock(); decoder_unlock();
} }
bool bool
dc_seek(struct notify *notify, double where) dc_seek(double where)
{ {
assert(dc.state != DECODE_STATE_START); assert(dc.state != DECODE_STATE_START);
assert(where >= 0.0); assert(where >= 0.0);
@ -135,7 +131,7 @@ dc_seek(struct notify *notify, double where)
dc.seek_where = where; dc.seek_where = where;
dc.seek_error = false; dc.seek_error = false;
dc_command(notify, DECODE_COMMAND_SEEK); dc_command(DECODE_COMMAND_SEEK);
if (dc.seek_error) if (dc.seek_error)
return false; return false;

View File

@ -30,8 +30,6 @@
#define DECODE_TYPE_FILE 0 #define DECODE_TYPE_FILE 0
#define DECODE_TYPE_URL 1 #define DECODE_TYPE_URL 1
struct notify;
enum decoder_state { enum decoder_state {
DECODE_STATE_STOP = 0, DECODE_STATE_STOP = 0,
DECODE_STATE_START, DECODE_STATE_START,
@ -205,19 +203,19 @@ decoder_current_song(void)
} }
void void
dc_command_wait(struct notify *notify); dc_command_wait(void);
void void
dc_start(struct notify *notify, struct song *song); dc_start(struct song *song);
void void
dc_start_async(struct song *song); dc_start_async(struct song *song);
void void
dc_stop(struct notify *notify); dc_stop(void);
bool bool
dc_seek(struct notify *notify, double where); dc_seek(double where);
void void
dc_quit(void); dc_quit(void);

View File

@ -58,10 +58,7 @@ need_chunks(struct input_stream *is, bool do_wait)
if ((is == NULL || decoder_input_buffer(is) <= 0) && do_wait) { if ((is == NULL || decoder_input_buffer(is) <= 0) && do_wait) {
decoder_wait(); decoder_wait();
player_signal();
decoder_unlock();
notify_signal(&pc.notify);
decoder_lock();
return dc.command; return dc.command;
} }

View File

@ -112,9 +112,7 @@ static void decoder_run_song(const struct song *song, const char *uri)
dc.state = DECODE_STATE_START; dc.state = DECODE_STATE_START;
dc.command = DECODE_COMMAND_NONE; dc.command = DECODE_COMMAND_NONE;
decoder_unlock(); player_signal();
notify_signal(&pc.notify);
decoder_lock();
/* wait for the input stream to become ready; its metadata /* wait for the input stream to become ready; its metadata
will be available then */ will be available then */
@ -294,17 +292,13 @@ static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
dc.command = DECODE_COMMAND_NONE; dc.command = DECODE_COMMAND_NONE;
decoder_unlock(); player_signal();
notify_signal(&pc.notify);
decoder_lock();
break; break;
case DECODE_COMMAND_STOP: case DECODE_COMMAND_STOP:
dc.command = DECODE_COMMAND_NONE; dc.command = DECODE_COMMAND_NONE;
decoder_unlock(); player_signal();
notify_signal(&pc.notify);
decoder_lock();
break; break;
case DECODE_COMMAND_NONE: case DECODE_COMMAND_NONE:

View File

@ -93,7 +93,7 @@ enum {
GThread *main_task; GThread *main_task;
GMainLoop *main_loop; GMainLoop *main_loop;
struct notify main_notify; GCond *main_cond;
static void static void
glue_daemonize_init(const struct options *options) glue_daemonize_init(const struct options *options)
@ -321,7 +321,7 @@ int main(int argc, char *argv[])
main_task = g_thread_self(); main_task = g_thread_self();
main_loop = g_main_loop_new(NULL, FALSE); main_loop = g_main_loop_new(NULL, FALSE);
notify_init(&main_notify); main_cond = g_cond_new();
event_pipe_init(); event_pipe_init();
event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
@ -415,7 +415,7 @@ int main(int argc, char *argv[])
sticker_global_finish(); sticker_global_finish();
#endif #endif
notify_deinit(&main_notify); g_cond_free(main_cond);
event_pipe_deinit(); event_pipe_deinit();
playlist_list_global_finish(); playlist_list_global_finish();

View File

@ -26,6 +26,6 @@ extern GThread *main_task;
extern GMainLoop *main_loop; extern GMainLoop *main_loop;
extern struct notify main_notify; extern GCond *main_cond;
#endif #endif

View File

@ -445,10 +445,15 @@ audio_output_all_check(void)
bool bool
audio_output_all_wait(unsigned threshold) audio_output_all_wait(unsigned threshold)
{ {
if (audio_output_all_check() < threshold) player_lock();
return true;
notify_wait(&pc.notify); if (audio_output_all_check() < threshold) {
player_unlock();
return true;
}
player_wait();
player_unlock();
return audio_output_all_check() < threshold; return audio_output_all_check() < threshold;
} }

View File

@ -359,7 +359,7 @@ ao_play(struct audio_output *ao)
ao->chunk_finished = true; ao->chunk_finished = true;
g_mutex_unlock(ao->mutex); g_mutex_unlock(ao->mutex);
notify_signal(&pc.notify); player_lock_signal();
g_mutex_lock(ao->mutex); g_mutex_lock(ao->mutex);
return true; return true;

View File

@ -18,6 +18,7 @@
*/ */
#include "player_control.h" #include "player_control.h"
#include "decoder_control.h"
#include "path.h" #include "path.h"
#include "log.h" #include "log.h"
#include "tag.h" #include "tag.h"
@ -35,7 +36,10 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
{ {
pc.buffer_chunks = buffer_chunks; pc.buffer_chunks = buffer_chunks;
pc.buffered_before_play = buffered_before_play; pc.buffered_before_play = buffered_before_play;
notify_init(&pc.notify);
pc.mutex = g_mutex_new();
pc.cond = g_cond_new();
pc.command = PLAYER_COMMAND_NONE; pc.command = PLAYER_COMMAND_NONE;
pc.error = PLAYER_ERROR_NOERROR; pc.error = PLAYER_ERROR_NOERROR;
pc.state = PLAYER_STATE_STOP; pc.state = PLAYER_STATE_STOP;
@ -44,7 +48,16 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
void pc_deinit(void) void pc_deinit(void)
{ {
notify_deinit(&pc.notify); g_cond_free(pc.cond);
g_mutex_free(pc.mutex);
}
void
player_wait_decoder(void)
{
/* during this function, the decoder lock is held, because
we're waiting for the decoder thread */
g_cond_wait(pc.cond, dc.mutex);
} }
void void
@ -56,15 +69,30 @@ pc_song_deleted(const struct song *song)
} }
} }
static void player_command(enum player_command cmd) static void
player_command_wait_locked(void)
{
while (pc.command != PLAYER_COMMAND_NONE) {
player_signal();
g_cond_wait(main_cond, pc.mutex);
}
}
static void
player_command_locked(enum player_command cmd)
{ {
assert(pc.command == PLAYER_COMMAND_NONE); assert(pc.command == PLAYER_COMMAND_NONE);
pc.command = cmd; pc.command = cmd;
while (pc.command != PLAYER_COMMAND_NONE) { player_command_wait_locked();
notify_signal(&pc.notify); }
notify_wait(&main_notify);
} static void
player_command(enum player_command cmd)
{
player_lock();
player_command_locked(cmd);
player_unlock();
} }
void void

View File

@ -88,10 +88,19 @@ struct player_control {
thread isn't running */ thread isn't running */
GThread *thread; GThread *thread;
struct notify notify; /**
volatile enum player_command command; * This lock protects #command, #state, #error.
volatile enum player_state state; */
volatile enum player_error error; GMutex *mutex;
/**
* Trigger this object after you have modified #command.
*/
GCond *cond;
enum player_command command;
enum player_state state;
enum player_error error;
uint16_t bit_rate; uint16_t bit_rate;
struct audio_format audio_format; struct audio_format audio_format;
float total_time; float total_time;
@ -109,6 +118,67 @@ void pc_init(unsigned buffer_chunks, unsigned buffered_before_play);
void pc_deinit(void); void pc_deinit(void);
/**
* Locks the #player_control object.
*/
static inline void
player_lock(void)
{
g_mutex_lock(pc.mutex);
}
/**
* Unlocks the #player_control object.
*/
static inline void
player_unlock(void)
{
g_mutex_unlock(pc.mutex);
}
/**
* Waits for a signal on the #player_control object. This function is
* only valid in the player thread. The object must be locked prior
* to calling this function.
*/
static inline void
player_wait(void)
{
g_cond_wait(pc.cond, pc.mutex);
}
/**
* Waits for a signal on the #player_control object. This function is
* only valid in the player thread. The #decoder_control object must
* be locked prior to calling this function.
*
* Note the small difference to the player_wait() function!
*/
void
player_wait_decoder(void);
/**
* Signals the #player_control object. The object should be locked
* prior to calling this function.
*/
static inline void
player_signal(void)
{
g_cond_signal(pc.cond);
}
/**
* Signals the #player_control object. The object is temporarily
* locked by this function.
*/
static inline void
player_lock_signal(void)
{
player_lock();
player_signal();
player_unlock();
}
/** /**
* Call this function when the specified song pointer is about to be * Call this function when the specified song pointer is about to be
* invalidated. This makes sure that player_control.errored_song does * invalidated. This makes sure that player_control.errored_song does

View File

@ -106,21 +106,30 @@ struct player {
static struct music_buffer *player_buffer; static struct music_buffer *player_buffer;
static void player_command_finished(void) static void player_command_finished_locked(void)
{ {
assert(pc.command != PLAYER_COMMAND_NONE); assert(pc.command != PLAYER_COMMAND_NONE);
pc.command = PLAYER_COMMAND_NONE; pc.command = PLAYER_COMMAND_NONE;
notify_signal(&main_notify); g_cond_signal(main_cond);
}
static void player_command_finished(void)
{
player_lock();
player_command_finished_locked();
player_unlock();
} }
/** /**
* Stop the decoder and clears (and frees) its music pipe. * Stop the decoder and clears (and frees) its music pipe.
*
* Player lock is not held.
*/ */
static void static void
player_dc_stop(struct player *player) player_dc_stop(struct player *player)
{ {
dc_stop(&pc.notify); dc_stop();
if (dc.pipe != NULL) { if (dc.pipe != NULL) {
/* clear and free the decoder pipe */ /* clear and free the decoder pipe */
@ -138,17 +147,23 @@ player_dc_stop(struct player *player)
* After the decoder has been started asynchronously, wait for the * After the decoder has been started asynchronously, wait for the
* "START" command to finish. The decoder may not be initialized yet, * "START" command to finish. The decoder may not be initialized yet,
* i.e. there is no audio_format information yet. * i.e. there is no audio_format information yet.
*
* The player lock is not held.
*/ */
static bool static bool
player_wait_for_decoder(struct player *player) player_wait_for_decoder(struct player *player)
{ {
dc_command_wait(&pc.notify); dc_command_wait();
if (decoder_lock_has_failed()) { if (decoder_lock_has_failed()) {
assert(dc.next_song == NULL || dc.next_song->uri != NULL); assert(dc.next_song == NULL || dc.next_song->uri != NULL);
player_lock();
pc.errored_song = dc.next_song; pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_FILE; pc.error = PLAYER_ERROR_FILE;
pc.next_song = NULL; pc.next_song = NULL;
player_unlock();
player->queued = false; player->queued = false;
return false; return false;
} }
@ -177,6 +192,8 @@ player_wait_for_decoder(struct player *player)
* The decoder has acknowledged the "START" command (see * The decoder has acknowledged the "START" command (see
* player_wait_for_decoder()). This function checks if the decoder * player_wait_for_decoder()). This function checks if the decoder
* initialization has completed yet. * initialization has completed yet.
*
* The player lock is not held.
*/ */
static bool static bool
player_check_decoder_startup(struct player *player) player_check_decoder_startup(struct player *player)
@ -234,8 +251,8 @@ player_check_decoder_startup(struct player *player)
} else { } else {
/* the decoder is not yet ready; wait /* the decoder is not yet ready; wait
some more */ some more */
player_wait_decoder();
decoder_unlock(); decoder_unlock();
notify_wait(&pc.notify);
return true; return true;
} }
@ -245,6 +262,8 @@ player_check_decoder_startup(struct player *player)
* Sends a chunk of silence to the audio outputs. This is called when * 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 * there is not enough decoded data in the pipe yet, to prevent
* underruns in the hardware buffers. * underruns in the hardware buffers.
*
* The player lock is not held.
*/ */
static bool static bool
player_send_silence(struct player *player) player_send_silence(struct player *player)
@ -281,6 +300,8 @@ player_send_silence(struct player *player)
/** /**
* This is the handler for the #PLAYER_COMMAND_SEEK command. * This is the handler for the #PLAYER_COMMAND_SEEK command.
*
* The player lock is not held.
*/ */
static bool player_seek_decoder(struct player *player) static bool player_seek_decoder(struct player *player)
{ {
@ -332,7 +353,7 @@ static bool player_seek_decoder(struct player *player)
if (where < 0.0) if (where < 0.0)
where = 0.0; where = 0.0;
ret = dc_seek(&pc.notify, where); ret = dc_seek(where);
if (!ret) { if (!ret) {
/* decoder failure */ /* decoder failure */
player_command_finished(); player_command_finished();
@ -353,6 +374,9 @@ static bool player_seek_decoder(struct player *player)
return true; return true;
} }
/**
* Player lock must be held before calling.
*/
static void player_process_command(struct player *player) static void player_process_command(struct player *player)
{ {
switch (pc.command) { switch (pc.command) {
@ -363,8 +387,10 @@ static void player_process_command(struct player *player)
break; break;
case PLAYER_COMMAND_UPDATE_AUDIO: case PLAYER_COMMAND_UPDATE_AUDIO:
player_unlock();
audio_output_all_enable_disable(); audio_output_all_enable_disable();
player_command_finished(); player_lock();
player_command_finished_locked();
break; break;
case PLAYER_COMMAND_QUEUE: case PLAYER_COMMAND_QUEUE:
@ -373,21 +399,28 @@ static void player_process_command(struct player *player)
assert(dc.pipe == NULL || dc.pipe == player->pipe); assert(dc.pipe == NULL || dc.pipe == player->pipe);
player->queued = true; player->queued = true;
player_command_finished(); player_command_finished_locked();
break; break;
case PLAYER_COMMAND_PAUSE: case PLAYER_COMMAND_PAUSE:
player_unlock();
player->paused = !player->paused; player->paused = !player->paused;
if (player->paused) { if (player->paused) {
audio_output_all_pause(); audio_output_all_pause();
player_lock();
pc.state = PLAYER_STATE_PAUSE; pc.state = PLAYER_STATE_PAUSE;
} else if (!audio_format_defined(&player->play_audio_format)) { } else if (!audio_format_defined(&player->play_audio_format)) {
/* the decoder hasn't provided an audio format /* the decoder hasn't provided an audio format
yet - don't open the audio device yet */ yet - don't open the audio device yet */
player_lock();
pc.state = PLAYER_STATE_PLAY; pc.state = PLAYER_STATE_PLAY;
} else if (audio_output_all_open(&player->play_audio_format, player_buffer)) { } else if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
/* unpaused, continue playing */ /* unpaused, continue playing */
player_lock();
pc.state = PLAYER_STATE_PLAY; pc.state = PLAYER_STATE_PLAY;
} else { } else {
/* the audio device has failed - rollback to /* the audio device has failed - rollback to
@ -397,13 +430,17 @@ static void player_process_command(struct player *player)
pc.error = PLAYER_ERROR_AUDIO; pc.error = PLAYER_ERROR_AUDIO;
player->paused = true; player->paused = true;
player_lock();
} }
player_command_finished(); player_command_finished_locked();
break; break;
case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_SEEK:
player_unlock();
player_seek_decoder(player); player_seek_decoder(player);
player_lock();
break; break;
case PLAYER_COMMAND_CANCEL: case PLAYER_COMMAND_CANCEL:
@ -415,26 +452,32 @@ static void player_process_command(struct player *player)
return; return;
} }
if (dc.pipe != NULL && dc.pipe != player->pipe) if (dc.pipe != NULL && dc.pipe != player->pipe) {
/* the decoder is already decoding the song - /* the decoder is already decoding the song -
stop it and reset the position */ stop it and reset the position */
player_unlock();
player_dc_stop(player); player_dc_stop(player);
player_lock();
}
pc.next_song = NULL; pc.next_song = NULL;
player->queued = false; player->queued = false;
player_command_finished(); player_command_finished_locked();
break; break;
case PLAYER_COMMAND_REFRESH: case PLAYER_COMMAND_REFRESH:
if (audio_format_defined(&player->play_audio_format) && if (audio_format_defined(&player->play_audio_format) &&
!player->paused) !player->paused) {
player_unlock();
audio_output_all_check(); audio_output_all_check();
player_lock();
}
pc.elapsed_time = audio_output_all_get_elapsed_time(); pc.elapsed_time = audio_output_all_get_elapsed_time();
if (pc.elapsed_time < 0.0) if (pc.elapsed_time < 0.0)
pc.elapsed_time = player->elapsed_time; pc.elapsed_time = player->elapsed_time;
player_command_finished(); player_command_finished_locked();
break; break;
} }
} }
@ -468,6 +511,8 @@ update_song_tag(struct song *song, const struct tag *new_tag)
* Plays a #music_chunk object (after applying software volume). If * Plays a #music_chunk object (after applying software volume). If
* it contains a (stream) tag, copy it to the current song, so MPD's * it contains a (stream) tag, copy it to the current song, so MPD's
* playlist reflects the new stream tag. * playlist reflects the new stream tag.
*
* Player lock is not held.
*/ */
static bool static bool
play_chunk(struct song *song, struct music_chunk *chunk, play_chunk(struct song *song, struct music_chunk *chunk,
@ -553,10 +598,9 @@ play_next_chunk(struct player *player)
} else { } else {
/* wait for the decoder */ /* wait for the decoder */
decoder_signal(); decoder_signal();
player_wait_decoder();
decoder_unlock(); decoder_unlock();
notify_wait(&pc.notify);
return true; return true;
} }
} }
@ -574,6 +618,8 @@ play_next_chunk(struct player *player)
if (!success) { if (!success) {
music_buffer_return(player_buffer, chunk); music_buffer_return(player_buffer, chunk);
player_lock();
pc.error = PLAYER_ERROR_AUDIO; pc.error = PLAYER_ERROR_AUDIO;
/* pause: the user may resume playback as soon as an /* pause: the user may resume playback as soon as an
@ -581,6 +627,8 @@ play_next_chunk(struct player *player)
pc.state = PLAYER_STATE_PAUSE; pc.state = PLAYER_STATE_PAUSE;
player->paused = true; player->paused = true;
player_unlock();
return false; return false;
} }
@ -602,6 +650,8 @@ play_next_chunk(struct player *player)
* has consumed all chunks of the current song, and we should start * has consumed all chunks of the current song, and we should start
* sending chunks from the next one. * sending chunks from the next one.
* *
* The player lock is not held.
*
* @return true on success, false on error (playback will be stopped) * @return true on success, false on error (playback will be stopped)
*/ */
static bool static bool
@ -643,31 +693,38 @@ static void do_play(void)
.elapsed_time = 0.0, .elapsed_time = 0.0,
}; };
player_unlock();
player.pipe = music_pipe_new(); player.pipe = music_pipe_new();
dc.buffer = player_buffer; dc.buffer = player_buffer;
dc.pipe = player.pipe; dc.pipe = player.pipe;
dc_start(&pc.notify, pc.next_song); dc_start(pc.next_song);
if (!player_wait_for_decoder(&player)) { if (!player_wait_for_decoder(&player)) {
player_dc_stop(&player); player_dc_stop(&player);
player_command_finished(); player_command_finished();
music_pipe_free(player.pipe); music_pipe_free(player.pipe);
event_pipe_emit(PIPE_EVENT_PLAYLIST); event_pipe_emit(PIPE_EVENT_PLAYLIST);
player_lock();
return; return;
} }
player_lock();
pc.state = PLAYER_STATE_PLAY; pc.state = PLAYER_STATE_PLAY;
player_command_finished(); player_command_finished_locked();
while (true) { while (true) {
player_process_command(&player); player_process_command(&player);
if (pc.command == PLAYER_COMMAND_STOP || if (pc.command == PLAYER_COMMAND_STOP ||
pc.command == PLAYER_COMMAND_EXIT || pc.command == PLAYER_COMMAND_EXIT ||
pc.command == PLAYER_COMMAND_CLOSE_AUDIO) { pc.command == PLAYER_COMMAND_CLOSE_AUDIO) {
player_unlock();
audio_output_all_cancel(); audio_output_all_cancel();
break; break;
} }
player_unlock();
if (player.buffering) { if (player.buffering) {
/* buffering at the start of the song - wait /* buffering at the start of the song - wait
until the buffer is large enough, to until the buffer is large enough, to
@ -683,7 +740,10 @@ static void do_play(void)
!player_send_silence(&player)) !player_send_silence(&player))
break; break;
notify_wait(&pc.notify); decoder_lock();
/* XXX race condition: check decoder again */
player_wait_decoder();
decoder_unlock();
continue; continue;
} else { } else {
/* buffering is complete */ /* buffering is complete */
@ -698,6 +758,8 @@ static void do_play(void)
success = player_check_decoder_startup(&player); success = player_check_decoder_startup(&player);
if (!success) if (!success)
break; break;
player_lock();
continue; continue;
} }
@ -741,9 +803,11 @@ static void do_play(void)
player.xfade = XFADE_DISABLED; player.xfade = XFADE_DISABLED;
} }
if (player.paused) if (player.paused) {
notify_wait(&pc.notify); player_lock();
else if (music_pipe_size(player.pipe) > 0) { player_wait();
continue;
} else if (music_pipe_size(player.pipe) > 0) {
/* at least one music chunk is ready - send it /* at least one music chunk is ready - send it
to the audio output */ to the audio output */
@ -773,6 +837,8 @@ static void do_play(void)
if (!player_send_silence(&player)) if (!player_send_silence(&player))
break; break;
} }
player_lock();
} }
player_dc_stop(&player); player_dc_stop(&player);
@ -783,8 +849,13 @@ static void do_play(void)
music_pipe_clear(player.pipe, player_buffer); music_pipe_clear(player.pipe, player_buffer);
music_pipe_free(player.pipe); music_pipe_free(player.pipe);
player_lock();
pc.state = PLAYER_STATE_STOP; pc.state = PLAYER_STATE_STOP;
player_unlock();
event_pipe_emit(PIPE_EVENT_PLAYLIST); event_pipe_emit(PIPE_EVENT_PLAYLIST);
player_lock();
} }
static gpointer player_task(G_GNUC_UNUSED gpointer arg) static gpointer player_task(G_GNUC_UNUSED gpointer arg)
@ -793,6 +864,8 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
player_buffer = music_buffer_new(pc.buffer_chunks); player_buffer = music_buffer_new(pc.buffer_chunks);
player_lock();
while (1) { while (1) {
switch (pc.command) { switch (pc.command) {
case PLAYER_COMMAND_QUEUE: case PLAYER_COMMAND_QUEUE:
@ -802,18 +875,25 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
break; break;
case PLAYER_COMMAND_STOP: case PLAYER_COMMAND_STOP:
player_unlock();
audio_output_all_cancel(); audio_output_all_cancel();
player_lock();
/* fall through */ /* fall through */
case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE: case PLAYER_COMMAND_PAUSE:
pc.next_song = NULL; pc.next_song = NULL;
player_command_finished(); player_command_finished_locked();
break; break;
case PLAYER_COMMAND_CLOSE_AUDIO: case PLAYER_COMMAND_CLOSE_AUDIO:
player_unlock();
audio_output_all_close(); audio_output_all_close();
player_command_finished();
player_lock();
player_command_finished_locked();
#ifndef NDEBUG #ifndef NDEBUG
/* in the DEBUG build, check for leaked /* in the DEBUG build, check for leaked
@ -826,33 +906,39 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
break; break;
case PLAYER_COMMAND_UPDATE_AUDIO: case PLAYER_COMMAND_UPDATE_AUDIO:
player_unlock();
audio_output_all_enable_disable(); audio_output_all_enable_disable();
player_command_finished(); player_lock();
player_command_finished_locked();
break; break;
case PLAYER_COMMAND_EXIT: case PLAYER_COMMAND_EXIT:
player_unlock();
dc_quit(); dc_quit();
audio_output_all_close(); audio_output_all_close();
music_buffer_free(player_buffer); music_buffer_free(player_buffer);
player_command_finished(); player_command_finished();
g_thread_exit(NULL); g_thread_exit(NULL);
break; break;
case PLAYER_COMMAND_CANCEL: case PLAYER_COMMAND_CANCEL:
pc.next_song = NULL; pc.next_song = NULL;
player_command_finished(); player_command_finished_locked();
break; break;
case PLAYER_COMMAND_REFRESH: case PLAYER_COMMAND_REFRESH:
/* no-op when not playing */ /* no-op when not playing */
player_command_finished(); player_command_finished_locked();
break; break;
case PLAYER_COMMAND_NONE: case PLAYER_COMMAND_NONE:
notify_wait(&pc.notify); player_wait();
break; break;
} }
} }
return NULL; return NULL;
} }