diff options
-rw-r--r-- | Makefile.am | 9 | ||||
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | configure.ac | 8 | ||||
-rw-r--r-- | mpd.service.in | 9 | ||||
-rw-r--r-- | src/decoder_api.c | 61 | ||||
-rw-r--r-- | src/decoder_control.c | 3 | ||||
-rw-r--r-- | src/decoder_control.h | 20 | ||||
-rw-r--r-- | src/decoder_thread.c | 2 | ||||
-rw-r--r-- | src/output_control.c | 3 | ||||
-rw-r--r-- | src/player_control.c | 3 | ||||
-rw-r--r-- | src/player_thread.c | 97 | ||||
-rw-r--r-- | src/playlist_song.c | 5 |
12 files changed, 173 insertions, 54 deletions
diff --git a/Makefile.am b/Makefile.am index 54e03c277..ccf74b9fd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -873,6 +873,15 @@ FILTER_SRC = \ # +# systemd unit +# + +if HAVE_SYSTEMD +systemdsystemunit_DATA = \ + mpd.service +endif + +# # Sparse code analysis # # sparse is a semantic parser @@ -37,7 +37,14 @@ ver 0.16.5 (2010/??/??) - ffmpeg: higher precision timestamps - ffmpeg: don't require key frame for seeking - fix CUE track seeking +* player: + - make seeking to CUE track more reliable + - the "seek" command works when MPD is stopped + - restore song position from state file (bug fix) + - fix crash that sometimes occurred when audio device fails on startup + - fix absolute path support in playlists * WIN32: close sockets properly +* install systemd service file if systemd is available ver 0.16.4 (2011/09/01) diff --git a/configure.ac b/configure.ac index 18e7e471f..98062eea6 100644 --- a/configure.ac +++ b/configure.ac @@ -34,6 +34,13 @@ AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes) AC_PROG_INSTALL AC_PROG_MAKE_SET PKG_PROG_PKG_CONFIG +AC_ARG_WITH([systemdsystemunitdir], + AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]), + [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]) +if test "x$with_systemdsystemunitdir" != xno; then + AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) +fi +AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ]) dnl --------------------------------------------------------------------------- dnl Declare Variables @@ -1617,5 +1624,6 @@ dnl Generate files dnl --------------------------------------------------------------------------- AC_OUTPUT(Makefile) AC_OUTPUT(doc/doxygen.conf) +AC_OUTPUT(mpd.service) echo 'MPD is ready for compilation, type "make" to begin.' diff --git a/mpd.service.in b/mpd.service.in new file mode 100644 index 000000000..9e9282fa6 --- /dev/null +++ b/mpd.service.in @@ -0,0 +1,9 @@ +[Unit] +Description=Music Player Daemon +After=sound.target + +[Service] +ExecStart=@prefix@/bin/mpd --no-daemon + +[Install] +WantedBy=multi-user.target diff --git a/src/decoder_api.c b/src/decoder_api.c index a34e19b1a..311f1f31b 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -77,30 +77,54 @@ decoder_initialized(struct decoder *decoder, } /** - * Returns the current decoder command. May return a "virtual" - * synthesized command, e.g. to seek to the beginning of the CUE - * track. + * Checks if we need an "initial seek". If so, then the initial seek + * is prepared, and the function returns true. */ G_GNUC_PURE -static enum decoder_command -decoder_get_virtual_command(struct decoder *decoder) +static bool +decoder_prepare_initial_seek(struct decoder *decoder) { const struct decoder_control *dc = decoder->dc; assert(dc->pipe != NULL); if (decoder->initial_seek_running) - return DECODE_COMMAND_SEEK; + /* initial seek has already begun - override any other + command */ + return true; if (decoder->initial_seek_pending) { if (dc->command == DECODE_COMMAND_NONE) { + /* begin initial seek */ + decoder->initial_seek_pending = false; decoder->initial_seek_running = true; - return DECODE_COMMAND_SEEK; + return true; } + /* skip initial seek when there's another command + (e.g. STOP) */ + decoder->initial_seek_pending = false; } + return false; +} + +/** + * Returns the current decoder command. May return a "virtual" + * synthesized command, e.g. to seek to the beginning of the CUE + * track. + */ +G_GNUC_PURE +static enum decoder_command +decoder_get_virtual_command(struct decoder *decoder) +{ + const struct decoder_control *dc = decoder->dc; + assert(dc->pipe != NULL); + + if (decoder_prepare_initial_seek(decoder)) + return DECODE_COMMAND_SEEK; + return dc->command; } @@ -130,7 +154,7 @@ decoder_command_finished(struct decoder *decoder) assert(music_pipe_empty(dc->pipe)); decoder->initial_seek_running = false; - decoder->timestamp = dc->song->start_ms / 1000.; + decoder->timestamp = dc->start_ms / 1000.; decoder_unlock(dc); return; } @@ -162,7 +186,7 @@ double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) assert(dc->pipe != NULL); if (decoder->initial_seek_running) - return dc->song->start_ms / 1000.; + return dc->start_ms / 1000.; assert(dc->command == DECODE_COMMAND_SEEK); @@ -177,10 +201,12 @@ void decoder_seek_error(struct decoder * decoder) assert(dc->pipe != NULL); - if (decoder->initial_seek_running) + if (decoder->initial_seek_running) { /* d'oh, we can't seek to the sub-song start position, what now? - no idea, ignoring the problem for now. */ + decoder->initial_seek_running = false; return; + } assert(dc->command == DECODE_COMMAND_SEEK); @@ -424,8 +450,8 @@ decoder_data(struct decoder *decoder, decoder->timestamp += (double)nbytes / audio_format_time_to_size(&dc->out_audio_format); - if (dc->song->end_ms > 0 && - decoder->timestamp >= dc->song->end_ms / 1000.0) + if (dc->end_ms > 0 && + decoder->timestamp >= dc->end_ms / 1000.0) /* the end of this range has been reached: stop decoding */ return DECODE_COMMAND_STOP; @@ -455,6 +481,14 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, update_stream_tag(decoder, is); + /* check if we're seeking */ + + if (decoder_prepare_initial_seek(decoder)) + /* during initial seek, no music chunk must be created + until seeking is finished; skip the rest of the + function here */ + return DECODE_COMMAND_SEEK; + /* send tag to music pipe */ if (decoder->stream_tag != NULL) { @@ -468,9 +502,6 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, /* send only the decoder tag */ cmd = do_send_tag(decoder, tag); - if (cmd == DECODE_COMMAND_NONE) - cmd = decoder_get_virtual_command(decoder); - return cmd; } diff --git a/src/decoder_control.c b/src/decoder_control.c index 685db6c22..70f34b331 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -96,6 +96,7 @@ dc_command_async(struct decoder_control *dc, enum decoder_command cmd) void dc_start(struct decoder_control *dc, struct song *song, + unsigned start_ms, unsigned end_ms, struct music_buffer *buffer, struct music_pipe *pipe) { assert(song != NULL); @@ -104,6 +105,8 @@ dc_start(struct decoder_control *dc, struct song *song, assert(music_pipe_empty(pipe)); dc->song = song; + dc->start_ms = start_ms; + dc->end_ms = end_ms; dc->buffer = buffer; dc->pipe = pipe; dc_command(dc, DECODE_COMMAND_START); diff --git a/src/decoder_control.h b/src/decoder_control.h index e1a718a59..566b153ee 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -85,6 +85,23 @@ struct decoder_control { */ const struct song *song; + /** + * The initial seek position (in milliseconds), e.g. to the + * start of a sub-track described by a CUE file. + * + * This attribute is set by dc_start(). + */ + unsigned start_ms; + + /** + * The decoder will stop when it reaches this position (in + * milliseconds). 0 means don't stop before the end of the + * file. + * + * This attribute is set by dc_start(). + */ + unsigned end_ms; + float total_time; /** the #music_chunk allocator */ @@ -229,11 +246,14 @@ decoder_current_song(const struct decoder_control *dc) * * @param the decoder * @param song the song to be decoded + * @param start_ms see #decoder_control + * @param end_ms see #decoder_control * @param pipe the pipe which receives the decoded chunks (owned by * the caller) */ void dc_start(struct decoder_control *dc, struct song *song, + unsigned start_ms, unsigned end_ms, struct music_buffer *buffer, struct music_pipe *pipe); void diff --git a/src/decoder_thread.c b/src/decoder_thread.c index dbead655d..c2bb655ea 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -380,7 +380,7 @@ decoder_run_song(struct decoder_control *dc, { struct decoder decoder = { .dc = dc, - .initial_seek_pending = song->start_ms > 0, + .initial_seek_pending = dc->start_ms > 0, .initial_seek_running = false, }; int ret; diff --git a/src/output_control.c b/src/output_control.c index 69553145a..7b95be49b 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -126,9 +126,6 @@ audio_output_disable(struct audio_output *ao) ao_lock_command(ao, AO_COMMAND_DISABLE); } -static void -audio_output_close_locked(struct audio_output *ao); - /** * Object must be locked (and unlocked) by the caller. */ diff --git a/src/player_control.c b/src/player_control.c index 51420a43f..d8d54dfd6 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -319,9 +319,6 @@ pc_seek(struct player_control *pc, struct song *song, float seek_time) { assert(song != NULL); - if (pc->state == PLAYER_STATE_STOP) - return false; - player_lock(pc); pc->next_song = song; pc->seek_where = seek_time; diff --git a/src/player_thread.c b/src/player_thread.c index 7696c22c0..c0243fa00 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -76,6 +76,14 @@ struct player { 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 */ struct song *song; @@ -150,7 +158,13 @@ player_dc_start(struct player *player, struct music_pipe *pipe) assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); assert(pc->next_song != NULL); - dc_start(dc, pc->next_song, player_buffer, pipe); + unsigned start_ms = pc->next_song->start_ms; + if (pc->command == PLAYER_COMMAND_SEEK) + start_ms += (unsigned)(pc->seek_where * 1000); + + dc_start(dc, pc->next_song, + start_ms, pc->next_song->end_ms, + player_buffer, pipe); } /** @@ -277,6 +291,46 @@ real_song_duration(const struct song *song, double decoder_duration) } /** + * Wrapper for audio_output_all_open(). Upon failure, it pauses the + * player. + * + * @return true on success + */ +static bool +player_open_output(struct player *player) +{ + struct player_control *pc = player->pc; + + assert(audio_format_defined(&player->play_audio_format)); + assert(pc->state == PLAYER_STATE_PLAY || + pc->state == PLAYER_STATE_PAUSE); + + if (audio_output_all_open(&player->play_audio_format, player_buffer)) { + player->output_open = true; + player->paused = false; + + player_lock(pc); + pc->state = PLAYER_STATE_PLAY; + player_unlock(pc); + + return true; + } else { + player->output_open = false; + + /* pause: the user may resume playback as soon as an + audio output becomes available */ + player->paused = true; + + player_lock(pc); + pc->error = PLAYER_ERROR_AUDIO; + pc->state = PLAYER_STATE_PAUSE; + player_unlock(pc); + + return false; + } +} + +/** * The decoder has acknowledged the "START" command (see * player_wait_for_decoder()). This function checks if the decoder * initialization has completed yet. @@ -308,7 +362,7 @@ player_check_decoder_startup(struct player *player) decoder_unlock(dc); - if (audio_format_defined(&player->play_audio_format) && + if (player->output_open && !audio_output_all_wait(pc, 1)) /* the output devices havn't finished playing all chunks yet - wait for that */ @@ -322,23 +376,12 @@ player_check_decoder_startup(struct player *player) player->play_audio_format = dc->out_audio_format; player->decoder_starting = false; - if (!player->paused && - !audio_output_all_open(&dc->out_audio_format, - player_buffer)) { + if (!player->paused && !player_open_output(player)) { char *uri = song_get_uri(dc->song); g_warning("problems opening audio device " "while playing \"%s\"", uri); g_free(uri); - player_lock(pc); - pc->error = PLAYER_ERROR_AUDIO; - - /* pause: the user may resume playback as soon - as an audio output becomes available */ - pc->state = PLAYER_STATE_PAUSE; - player_unlock(pc); - - player->paused = true; return true; } @@ -363,6 +406,7 @@ player_check_decoder_startup(struct player *player) static bool player_send_silence(struct player *player) { + assert(player->output_open); assert(audio_format_defined(&player->play_audio_format)); struct music_chunk *chunk = music_buffer_allocate(player_buffer); @@ -520,17 +564,8 @@ static void player_process_command(struct player *player) player_lock(pc); pc->state = PLAYER_STATE_PLAY; - } else if (audio_output_all_open(&player->play_audio_format, player_buffer)) { - /* unpaused, continue playing */ - player_lock(pc); - - pc->state = PLAYER_STATE_PLAY; } else { - /* the audio device has failed - rollback to - pause mode */ - pc->error = PLAYER_ERROR_AUDIO; - - player->paused = true; + player_open_output(player); player_lock(pc); } @@ -567,8 +602,7 @@ static void player_process_command(struct player *player) break; case PLAYER_COMMAND_REFRESH: - if (audio_format_defined(&player->play_audio_format) && - !player->paused) { + if (player->output_open && !player->paused) { player_unlock(pc); audio_output_all_check(); player_lock(pc); @@ -823,6 +857,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) .decoder_starting = false, .paused = false, .queued = true, + .output_open = false, .song = NULL, .xfade = XFADE_UNKNOWN, .cross_fading = false, @@ -847,6 +882,10 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) player_lock(pc); pc->state = PLAYER_STATE_PLAY; + + if (pc->command == PLAYER_COMMAND_SEEK) + player.elapsed_time = pc->seek_where; + player_command_finished_locked(pc); while (true) { @@ -871,7 +910,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) /* not enough decoded buffer space yet */ if (!player.paused && - audio_format_defined(&player.play_audio_format) && + player.output_open && audio_output_all_check() < 4 && !player_send_silence(&player)) break; @@ -976,7 +1015,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) audio_output_all_drain(); break; } - } else { + } else if (player.output_open) { /* the decoder is too busy and hasn't provided new PCM data in time: send silence (if the output pipe is empty) */ @@ -1025,6 +1064,7 @@ player_task(gpointer arg) while (1) { switch (pc->command) { + case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_QUEUE: assert(pc->next_song != NULL); @@ -1038,7 +1078,6 @@ player_task(gpointer arg) /* fall through */ - case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_PAUSE: pc->next_song = NULL; player_command_finished_locked(pc); diff --git a/src/playlist_song.c b/src/playlist_song.c index 8c966d549..afdc0cf9c 100644 --- a/src/playlist_song.c +++ b/src/playlist_song.c @@ -115,9 +115,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri, if (g_path_is_absolute(uri)) { /* XXX fs_charset vs utf8? */ - char *prefix = base_uri != NULL - ? map_uri_fs(base_uri) - : map_directory_fs(db_get_root()); + char *prefix = map_directory_fs(db_get_root()); if (prefix != NULL && g_str_has_prefix(uri, prefix) && uri[strlen(prefix)] == '/') @@ -130,6 +128,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri, return NULL; } + base_uri = NULL; g_free(prefix); } |