Commit 5c0576ca authored by Max Kellermann's avatar Max Kellermann

Merge branch 'v0.16.x'

Conflicts: configure.ac src/player_control.c src/player_thread.c src/playlist_song.c
parents 4e909f94 039b3544
...@@ -873,6 +873,15 @@ FILTER_SRC = \ ...@@ -873,6 +873,15 @@ FILTER_SRC = \
# #
# systemd unit
#
if HAVE_SYSTEMD
systemdsystemunit_DATA = \
mpd.service
endif
#
# Sparse code analysis # Sparse code analysis
# #
# sparse is a semantic parser # sparse is a semantic parser
......
...@@ -37,7 +37,14 @@ ver 0.16.5 (2010/??/??) ...@@ -37,7 +37,14 @@ ver 0.16.5 (2010/??/??)
- ffmpeg: higher precision timestamps - ffmpeg: higher precision timestamps
- ffmpeg: don't require key frame for seeking - ffmpeg: don't require key frame for seeking
- fix CUE track 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 * WIN32: close sockets properly
* install systemd service file if systemd is available
ver 0.16.4 (2011/09/01) ver 0.16.4 (2011/09/01)
......
...@@ -34,6 +34,13 @@ AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes) ...@@ -34,6 +34,13 @@ AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes)
AC_PROG_INSTALL AC_PROG_INSTALL
AC_PROG_MAKE_SET AC_PROG_MAKE_SET
PKG_PROG_PKG_CONFIG 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 ---------------------------------------------------------------------------
dnl Declare Variables dnl Declare Variables
...@@ -1617,5 +1624,6 @@ dnl Generate files ...@@ -1617,5 +1624,6 @@ dnl Generate files
dnl --------------------------------------------------------------------------- dnl ---------------------------------------------------------------------------
AC_OUTPUT(Makefile) AC_OUTPUT(Makefile)
AC_OUTPUT(doc/doxygen.conf) AC_OUTPUT(doc/doxygen.conf)
AC_OUTPUT(mpd.service)
echo 'MPD is ready for compilation, type "make" to begin.' echo 'MPD is ready for compilation, type "make" to begin.'
[Unit]
Description=Music Player Daemon
After=sound.target
[Service]
ExecStart=@prefix@/bin/mpd --no-daemon
[Install]
WantedBy=multi-user.target
...@@ -77,30 +77,54 @@ decoder_initialized(struct decoder *decoder, ...@@ -77,30 +77,54 @@ decoder_initialized(struct decoder *decoder,
} }
/** /**
* Returns the current decoder command. May return a "virtual" * Checks if we need an "initial seek". If so, then the initial seek
* synthesized command, e.g. to seek to the beginning of the CUE * is prepared, and the function returns true.
* track.
*/ */
G_GNUC_PURE G_GNUC_PURE
static enum decoder_command static bool
decoder_get_virtual_command(struct decoder *decoder) decoder_prepare_initial_seek(struct decoder *decoder)
{ {
const struct decoder_control *dc = decoder->dc; const struct decoder_control *dc = decoder->dc;
assert(dc->pipe != NULL); assert(dc->pipe != NULL);
if (decoder->initial_seek_running) 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 (decoder->initial_seek_pending) {
if (dc->command == DECODE_COMMAND_NONE) { if (dc->command == DECODE_COMMAND_NONE) {
/* begin initial seek */
decoder->initial_seek_pending = false; decoder->initial_seek_pending = false;
decoder->initial_seek_running = true; 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; 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; return dc->command;
} }
...@@ -130,7 +154,7 @@ decoder_command_finished(struct decoder *decoder) ...@@ -130,7 +154,7 @@ decoder_command_finished(struct decoder *decoder)
assert(music_pipe_empty(dc->pipe)); assert(music_pipe_empty(dc->pipe));
decoder->initial_seek_running = false; decoder->initial_seek_running = false;
decoder->timestamp = dc->song->start_ms / 1000.; decoder->timestamp = dc->start_ms / 1000.;
decoder_unlock(dc); decoder_unlock(dc);
return; return;
} }
...@@ -162,7 +186,7 @@ double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) ...@@ -162,7 +186,7 @@ double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
assert(dc->pipe != NULL); assert(dc->pipe != NULL);
if (decoder->initial_seek_running) if (decoder->initial_seek_running)
return dc->song->start_ms / 1000.; return dc->start_ms / 1000.;
assert(dc->command == DECODE_COMMAND_SEEK); assert(dc->command == DECODE_COMMAND_SEEK);
...@@ -177,10 +201,12 @@ void decoder_seek_error(struct decoder * decoder) ...@@ -177,10 +201,12 @@ void decoder_seek_error(struct decoder * decoder)
assert(dc->pipe != NULL); 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, /* d'oh, we can't seek to the sub-song start position,
what now? - no idea, ignoring the problem for now. */ what now? - no idea, ignoring the problem for now. */
decoder->initial_seek_running = false;
return; return;
}
assert(dc->command == DECODE_COMMAND_SEEK); assert(dc->command == DECODE_COMMAND_SEEK);
...@@ -424,8 +450,8 @@ decoder_data(struct decoder *decoder, ...@@ -424,8 +450,8 @@ decoder_data(struct decoder *decoder,
decoder->timestamp += (double)nbytes / decoder->timestamp += (double)nbytes /
audio_format_time_to_size(&dc->out_audio_format); audio_format_time_to_size(&dc->out_audio_format);
if (dc->song->end_ms > 0 && if (dc->end_ms > 0 &&
decoder->timestamp >= dc->song->end_ms / 1000.0) decoder->timestamp >= dc->end_ms / 1000.0)
/* the end of this range has been reached: /* the end of this range has been reached:
stop decoding */ stop decoding */
return DECODE_COMMAND_STOP; return DECODE_COMMAND_STOP;
...@@ -455,6 +481,14 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, ...@@ -455,6 +481,14 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
update_stream_tag(decoder, 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 */ /* send tag to music pipe */
if (decoder->stream_tag != NULL) { if (decoder->stream_tag != NULL) {
...@@ -468,9 +502,6 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, ...@@ -468,9 +502,6 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
/* send only the decoder tag */ /* send only the decoder tag */
cmd = do_send_tag(decoder, tag); cmd = do_send_tag(decoder, tag);
if (cmd == DECODE_COMMAND_NONE)
cmd = decoder_get_virtual_command(decoder);
return cmd; return cmd;
} }
......
...@@ -96,6 +96,7 @@ dc_command_async(struct decoder_control *dc, enum decoder_command cmd) ...@@ -96,6 +96,7 @@ dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
void void
dc_start(struct decoder_control *dc, struct song *song, dc_start(struct decoder_control *dc, struct song *song,
unsigned start_ms, unsigned end_ms,
struct music_buffer *buffer, struct music_pipe *pipe) struct music_buffer *buffer, struct music_pipe *pipe)
{ {
assert(song != NULL); assert(song != NULL);
...@@ -104,6 +105,8 @@ dc_start(struct decoder_control *dc, struct song *song, ...@@ -104,6 +105,8 @@ dc_start(struct decoder_control *dc, struct song *song,
assert(music_pipe_empty(pipe)); assert(music_pipe_empty(pipe));
dc->song = song; dc->song = song;
dc->start_ms = start_ms;
dc->end_ms = end_ms;
dc->buffer = buffer; dc->buffer = buffer;
dc->pipe = pipe; dc->pipe = pipe;
dc_command(dc, DECODE_COMMAND_START); dc_command(dc, DECODE_COMMAND_START);
......
...@@ -85,6 +85,23 @@ struct decoder_control { ...@@ -85,6 +85,23 @@ struct decoder_control {
*/ */
const struct song *song; 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; float total_time;
/** the #music_chunk allocator */ /** the #music_chunk allocator */
...@@ -229,11 +246,14 @@ decoder_current_song(const struct decoder_control *dc) ...@@ -229,11 +246,14 @@ decoder_current_song(const struct decoder_control *dc)
* *
* @param the decoder * @param the decoder
* @param song the song to be decoded * @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 * @param pipe the pipe which receives the decoded chunks (owned by
* the caller) * the caller)
*/ */
void void
dc_start(struct decoder_control *dc, struct song *song, dc_start(struct decoder_control *dc, struct song *song,
unsigned start_ms, unsigned end_ms,
struct music_buffer *buffer, struct music_pipe *pipe); struct music_buffer *buffer, struct music_pipe *pipe);
void void
......
...@@ -380,7 +380,7 @@ decoder_run_song(struct decoder_control *dc, ...@@ -380,7 +380,7 @@ decoder_run_song(struct decoder_control *dc,
{ {
struct decoder decoder = { struct decoder decoder = {
.dc = dc, .dc = dc,
.initial_seek_pending = song->start_ms > 0, .initial_seek_pending = dc->start_ms > 0,
.initial_seek_running = false, .initial_seek_running = false,
}; };
int ret; int ret;
......
...@@ -126,9 +126,6 @@ audio_output_disable(struct audio_output *ao) ...@@ -126,9 +126,6 @@ audio_output_disable(struct audio_output *ao)
ao_lock_command(ao, AO_COMMAND_DISABLE); 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. * Object must be locked (and unlocked) by the caller.
*/ */
......
...@@ -319,9 +319,6 @@ pc_seek(struct player_control *pc, struct song *song, float seek_time) ...@@ -319,9 +319,6 @@ pc_seek(struct player_control *pc, struct song *song, float seek_time)
{ {
assert(song != NULL); assert(song != NULL);
if (pc->state == PLAYER_STATE_STOP)
return false;
player_lock(pc); player_lock(pc);
pc->next_song = song; pc->next_song = song;
pc->seek_where = seek_time; pc->seek_where = seek_time;
......
...@@ -76,6 +76,14 @@ struct player { ...@@ -76,6 +76,14 @@ struct player {
bool queued; 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 * the song currently being played
*/ */
struct song *song; struct song *song;
...@@ -150,7 +158,13 @@ player_dc_start(struct player *player, struct music_pipe *pipe) ...@@ -150,7 +158,13 @@ player_dc_start(struct player *player, struct music_pipe *pipe)
assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
assert(pc->next_song != NULL); 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) ...@@ -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 * 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.
...@@ -308,7 +362,7 @@ player_check_decoder_startup(struct player *player) ...@@ -308,7 +362,7 @@ player_check_decoder_startup(struct player *player)
decoder_unlock(dc); decoder_unlock(dc);
if (audio_format_defined(&player->play_audio_format) && if (player->output_open &&
!audio_output_all_wait(pc, 1)) !audio_output_all_wait(pc, 1))
/* the output devices havn't finished playing /* the output devices havn't finished playing
all chunks yet - wait for that */ all chunks yet - wait for that */
...@@ -322,23 +376,12 @@ player_check_decoder_startup(struct player *player) ...@@ -322,23 +376,12 @@ player_check_decoder_startup(struct player *player)
player->play_audio_format = dc->out_audio_format; player->play_audio_format = dc->out_audio_format;
player->decoder_starting = false; player->decoder_starting = false;
if (!player->paused && if (!player->paused && !player_open_output(player)) {
!audio_output_all_open(&dc->out_audio_format,
player_buffer)) {
char *uri = song_get_uri(dc->song); char *uri = song_get_uri(dc->song);
g_warning("problems opening audio device " g_warning("problems opening audio device "
"while playing \"%s\"", uri); "while playing \"%s\"", uri);
g_free(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; return true;
} }
...@@ -363,6 +406,7 @@ player_check_decoder_startup(struct player *player) ...@@ -363,6 +406,7 @@ player_check_decoder_startup(struct player *player)
static bool static bool
player_send_silence(struct player *player) player_send_silence(struct player *player)
{ {
assert(player->output_open);
assert(audio_format_defined(&player->play_audio_format)); assert(audio_format_defined(&player->play_audio_format));
struct music_chunk *chunk = music_buffer_allocate(player_buffer); struct music_chunk *chunk = music_buffer_allocate(player_buffer);
...@@ -520,17 +564,8 @@ static void player_process_command(struct player *player) ...@@ -520,17 +564,8 @@ static void player_process_command(struct player *player)
player_lock(pc); player_lock(pc);
pc->state = PLAYER_STATE_PLAY; 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 { } else {
/* the audio device has failed - rollback to player_open_output(player);
pause mode */
pc->error = PLAYER_ERROR_AUDIO;
player->paused = true;
player_lock(pc); player_lock(pc);
} }
...@@ -567,8 +602,7 @@ static void player_process_command(struct player *player) ...@@ -567,8 +602,7 @@ static void player_process_command(struct player *player)
break; break;
case PLAYER_COMMAND_REFRESH: case PLAYER_COMMAND_REFRESH:
if (audio_format_defined(&player->play_audio_format) && if (player->output_open && !player->paused) {
!player->paused) {
player_unlock(pc); player_unlock(pc);
audio_output_all_check(); audio_output_all_check();
player_lock(pc); player_lock(pc);
...@@ -823,6 +857,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) ...@@ -823,6 +857,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
.decoder_starting = false, .decoder_starting = false,
.paused = false, .paused = false,
.queued = true, .queued = true,
.output_open = false,
.song = NULL, .song = NULL,
.xfade = XFADE_UNKNOWN, .xfade = XFADE_UNKNOWN,
.cross_fading = false, .cross_fading = false,
...@@ -847,6 +882,10 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) ...@@ -847,6 +882,10 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
player_lock(pc); player_lock(pc);
pc->state = PLAYER_STATE_PLAY; pc->state = PLAYER_STATE_PLAY;
if (pc->command == PLAYER_COMMAND_SEEK)
player.elapsed_time = pc->seek_where;
player_command_finished_locked(pc); player_command_finished_locked(pc);
while (true) { while (true) {
...@@ -871,7 +910,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) ...@@ -871,7 +910,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
/* not enough decoded buffer space yet */ /* not enough decoded buffer space yet */
if (!player.paused && if (!player.paused &&
audio_format_defined(&player.play_audio_format) && player.output_open &&
audio_output_all_check() < 4 && audio_output_all_check() < 4 &&
!player_send_silence(&player)) !player_send_silence(&player))
break; break;
...@@ -976,7 +1015,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc) ...@@ -976,7 +1015,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
audio_output_all_drain(); audio_output_all_drain();
break; break;
} }
} else { } else if (player.output_open) {
/* the decoder is too busy and hasn't provided /* the decoder is too busy and hasn't provided
new PCM data in time: send silence (if the new PCM data in time: send silence (if the
output pipe is empty) */ output pipe is empty) */
...@@ -1025,6 +1064,7 @@ player_task(gpointer arg) ...@@ -1025,6 +1064,7 @@ player_task(gpointer arg)
while (1) { while (1) {
switch (pc->command) { switch (pc->command) {
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_QUEUE: case PLAYER_COMMAND_QUEUE:
assert(pc->next_song != NULL); assert(pc->next_song != NULL);
...@@ -1038,7 +1078,6 @@ player_task(gpointer arg) ...@@ -1038,7 +1078,6 @@ player_task(gpointer arg)
/* fall through */ /* fall through */
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE: case PLAYER_COMMAND_PAUSE:
pc->next_song = NULL; pc->next_song = NULL;
player_command_finished_locked(pc); player_command_finished_locked(pc);
......
...@@ -115,9 +115,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri, ...@@ -115,9 +115,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
if (g_path_is_absolute(uri)) { if (g_path_is_absolute(uri)) {
/* XXX fs_charset vs utf8? */ /* XXX fs_charset vs utf8? */
char *prefix = base_uri != NULL char *prefix = map_directory_fs(db_get_root());
? map_uri_fs(base_uri)
: map_directory_fs(db_get_root());
if (prefix != NULL && g_str_has_prefix(uri, prefix) && if (prefix != NULL && g_str_has_prefix(uri, prefix) &&
uri[strlen(prefix)] == '/') uri[strlen(prefix)] == '/')
...@@ -130,6 +128,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri, ...@@ -130,6 +128,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
return NULL; return NULL;
} }
base_uri = NULL;
g_free(prefix); g_free(prefix);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment