Merge branch 'v0.16.x'
Conflicts: configure.ac src/player_control.c src/player_thread.c src/playlist_song.c
This commit is contained in:
commit
5c0576ca55
@ -872,6 +872,15 @@ FILTER_SRC = \
|
||||
src/filter/volume_filter_plugin.c
|
||||
|
||||
|
||||
#
|
||||
# systemd unit
|
||||
#
|
||||
|
||||
if HAVE_SYSTEMD
|
||||
systemdsystemunit_DATA = \
|
||||
mpd.service
|
||||
endif
|
||||
|
||||
#
|
||||
# Sparse code analysis
|
||||
#
|
||||
|
7
NEWS
7
NEWS
@ -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)
|
||||
|
@ -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.'
|
||||
|
9
mpd.service.in
Normal file
9
mpd.service.in
Normal file
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Music Player Daemon
|
||||
After=sound.target
|
||||
|
||||
[Service]
|
||||
ExecStart=@prefix@/bin/mpd --no-daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -76,6 +76,40 @@ decoder_initialized(struct decoder *decoder,
|
||||
&af_string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we need an "initial seek". If so, then the initial seek
|
||||
* is prepared, and the function returns true.
|
||||
*/
|
||||
G_GNUC_PURE
|
||||
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)
|
||||
/* 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 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
|
||||
@ -88,19 +122,9 @@ decoder_get_virtual_command(struct decoder *decoder)
|
||||
const struct decoder_control *dc = decoder->dc;
|
||||
assert(dc->pipe != NULL);
|
||||
|
||||
if (decoder->initial_seek_running)
|
||||
if (decoder_prepare_initial_seek(decoder))
|
||||
return DECODE_COMMAND_SEEK;
|
||||
|
||||
if (decoder->initial_seek_pending) {
|
||||
if (dc->command == DECODE_COMMAND_NONE) {
|
||||
decoder->initial_seek_pending = false;
|
||||
decoder->initial_seek_running = true;
|
||||
return DECODE_COMMAND_SEEK;
|
||||
}
|
||||
|
||||
decoder->initial_seek_pending = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -75,6 +75,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
|
||||
*/
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -276,6 +290,46 @@ real_song_duration(const struct song *song, double decoder_duration)
|
||||
return decoder_duration - song->start_ms / 1000.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -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);
|
||||
@ -519,18 +563,9 @@ static void player_process_command(struct player *player)
|
||||
yet - don't open the audio device yet */
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user