Compare commits

...

27 Commits

Author SHA1 Message Date
Avuton Olrich
57e95ea6f4 mpd version 0.15.10 2010-05-30 08:59:00 -07:00
Max Kellermann
442d2e74e3 decoder/mad: fix buffer variable name on !HAVE_ID3TAG 2010-05-30 17:27:03 +02:00
Max Kellermann
28736414a8 input/mms: initialize the "eof" attribute 2010-05-18 21:11:00 +02:00
Max Kellermann
e98bd55cbf input/mms: fix memory leak in error handler 2010-05-18 20:57:57 +02:00
Max Kellermann
a1a03deed2 decoder/mad: properly calculate ID3 size without libid3tag
Without libid3tag, we were trying to skip the ID3 frame (since
0.15.2).  Its length however was not calculated at all, we were just
dropping everything from the current input buffer.  This lead to the
first few seconds of the file being skipped.  This patch attempts to
calculate the ID3v2 frame size with the formula from:

 http://www.id3.org/id3v2.4.0-structure 3.1 and 6.2
2010-04-13 08:51:29 +02:00
Avuton Olrich
0dcd865c2e Modify version string to post-release version 0.15.10~git 2010-03-21 17:25:18 -07:00
Avuton Olrich
d612e5e0ab mpd version 0.15.9 2010-03-21 17:25:18 -07:00
Aleksei Kaveshnikov
73ba4ea3da decoder/mpcdec: fix replay gain formula with v8
"When playing musepack files with mpd v0.15.8, rg seems to have no effect.

Using sample file below, mpd says 'computing ReplayGain album scale with gain 122.879997, peak 0.549150'.

One thing though, if I build mpd against old libmpcdec-1.2.6, rg works
as expected: 'computing ReplayGain album scale with gain 16.820000,
peak 0.099765'"
2010-03-19 10:26:08 +01:00
Max Kellermann
cbfaa4a266 player_thread: postpone song tags during cross-fade
Previously, tags of the new song being cross-faded in were sent
immediately.  That can cause wrong information being displayed,
because the "previous" song might send its tag at the end again,
overriding the "next" song's tag.  This patch saves & merges the tag
of the next song, and sends it when cross-fading is finished, and the
next song really starts.
2010-03-17 23:14:54 +01:00
Max Kellermann
2e72a9b262 tag: added function tag_merge_replace()
Like tag_merge(), but can deal with NULL parameters, and frees both
tag objects.
2010-03-17 23:12:21 +01:00
Piotr Gozdur
96033e4b4e decoder/mpcdec: fix negative shift on fixed-point samples
"There is a bug in fixed-point musepack (musepack_src_r435) playback.
In floating-point audio is OK but in fixed audio is distorted.  I have
made a patch for this"
2010-03-17 17:54:21 +01:00
Max Kellermann
9134169e37 playlist: fix single+repeat in random mode
With single+repeat enabled, it is expected that MPD repeats the
current song over andd over.  With random mode also enabled, this
didn't work, because the song order was shuffled internally.  This
patch adds a special check for this case.
2010-03-07 18:58:44 +01:00
Max Kellermann
35c5a371ea decoder/mad: fix crash when seeking at end of song
Removed the decoder_command_finished() call at the end of
mp3_decode().  This is invalid, because decoder_command_finished() has
already been called in mp3_read().
2010-02-27 18:35:31 +01:00
Avuton Olrich
728c66e7e3 Modify version string to post-release version 0.15.9~git 2010-01-18 07:41:15 +01:00
Avuton Olrich
760569fc66 mpd version 0.15.8 2010-01-18 07:41:08 +01:00
Max Kellermann
2579a2f924 decoder/ffmpeg: added more MIME types
Taken from the ffmpeg sources.
2010-01-17 16:00:14 +01:00
Max Kellermann
a7664b98ba Makefile.am: link test/run_decoder and test/read_tags with timer.c
Needed for the fluidsynth decoder plugin.
2010-01-17 11:21:35 +01:00
Max Kellermann
1b441837f1 decoder/ffmpeg: append file name suffix to virtual stream URL
To allow libavformat to detect the format of the input file, append
the suffix of the input file to the URL of the virtual stream.  This
specifically enables the "shorten" codec, which is supported by
libavformat/raw.c, detected only by the suffix.
2010-01-17 02:36:07 +01:00
Max Kellermann
e43bf52cbb configure.ac: disable -Wdeclaration-after-statement
Allow declaration after statement.
2010-01-17 02:36:03 +01:00
Max Kellermann
8ba08edd0e queue: don't repeat current song in consume mode
Check consume mode in queue_next_order(), because the current song
would be deleted as soon as it's finished; it cannot be played again.
2010-01-16 20:58:24 +01:00
Max Kellermann
90d16af66a decoder_thread: fix CUE track playback
The patch "input/file: don't fall back to parent directory" introduced
a regression: when trying to play a CUE track, decoder_run_song()
tries to open the file as a stream first, but this fails, because the
path is virtual.

This patch fixes decoder_run_song() (instead of reverting the previous
patch) to accept input_stream_open() failures if the song is a local
file.  It passes the responsibility to handle non-existing files to
the decoder's file_decode() method.
2010-01-16 19:20:11 +01:00
Max Kellermann
777bd7c1e1 NEWS: added missing entry 2010-01-16 18:41:34 +01:00
Max Kellermann
959f94b06c dbUtils: return empty tag value only if no value was found
This fixes a regression in the patch "return multiple tag values per
song": even when the song has values for the specified tag type, the
empty string gets added to the set, because the "return" was removed.
This patch adds a flag which remembers whether at least one value was
found.
2010-01-02 19:24:31 +01:00
Max Kellermann
4419e5b90d input/curl: removed the built-in rewinding code
This has been reimplemented in the "rewind" input plugin.
2009-12-30 22:52:24 +01:00
Max Kellermann
c88f95a2ea input/rewind: new input_stream wrapper to allow stream rewinding
This replaces the rewinding buffer code from the CURL input plugin.
It is more generic, and allows rewinding even when the server sends
Icy-Metadata (which would have been too difficult to implement within
the CURL plugin).

This is a rather complex patch for the stable branch (v0.15.x), but it
fixes a serious problem: the "vorbis" decoder plugin was unable to
play streams with Icy-Metadata, because it couldn't rewind the stream
after detecting the codec (Vorbis vs. FLAC).
2009-12-29 23:55:40 +01:00
Max Kellermann
c7d099c757 decoder/{ffmpeg,flac,vorbis}: added more flac/vorbis MIME types
Support deprecated MIME types such as "audio/x-ogg".  Support new
types such as "audio/flac".
2009-12-29 22:33:46 +01:00
Avuton Olrich
d38c09051c Modify version string to post-release version 0.15.8~git 2009-12-27 08:31:47 -08:00
23 changed files with 521 additions and 232 deletions

@@ -75,6 +75,7 @@ mpd_headers = \
src/input_stream.h \
src/input/file_input_plugin.h \
src/input/curl_input_plugin.h \
src/input/rewind_input_plugin.h \
src/input/lastfm_input_plugin.h \
src/input/mms_input_plugin.h \
src/icy_server.h \
@@ -467,7 +468,9 @@ INPUT_SRC = \
src/input/file_input_plugin.c
if HAVE_CURL
INPUT_SRC += src/input/curl_input_plugin.c src/icy_metadata.c
INPUT_SRC += src/input/curl_input_plugin.c \
src/input/rewind_input_plugin.c \
src/icy_metadata.c
endif
if ENABLE_LASTFM
@@ -637,6 +640,7 @@ test_run_decoder_SOURCES = test/run_decoder.c \
src/tag.c src/tag_pool.c \
src/replay_gain.c \
src/uri.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \
@@ -656,6 +660,7 @@ test_read_tags_SOURCES = test/read_tags.c \
src/tag.c src/tag_pool.c \
src/replay_gain.c \
src/uri.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \

28
NEWS

@@ -1,3 +1,31 @@
ver 0.15.10 (2010/05/30)
* input:
- mms: fix memory leak in error handler
- mms: initialize the "eof" attribute
* decoders:
- mad: properly calculate ID3 size without libid3tag
ver 0.15.9 (2010/03/21)
* decoders:
- mad: fix crash when seeking at end of song
- mpcdec: fix negative shift on fixed-point samples
- mpcdec: fix replay gain formula with v8
* playlist: fix single+repeat in random mode
* player: postpone song tags during cross-fade
ver 0.15.8 (2010/01/17)
* input:
- curl: allow rewinding with Icy-Metadata
* decoders:
- ffmpeg, flac, vorbis: added more flac/vorbis MIME types
- ffmpeg: enabled libavformat's file name extension detection
* dbUtils: return empty tag value only if no value was found
* decoder_thread: fix CUE track playback
* queue: don't repeat current song in consume mode
ver 0.15.7 (2009/12/27)
* archive:
- close archive when stream is closed

@@ -1,5 +1,5 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.15.7, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.15.10, musicpd-dev-team@lists.sourceforge.net)
AC_CONFIG_SRCDIR([src/main.c])
AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
AM_CONFIG_HEADER(config.h)
@@ -1139,7 +1139,6 @@ then
MPD_CHECK_FLAG([-Wextra])
MPD_CHECK_FLAG([-Wno-deprecated-declarations])
MPD_CHECK_FLAG([-Wmissing-prototypes])
MPD_CHECK_FLAG([-Wdeclaration-after-statement])
MPD_CHECK_FLAG([-Wshadow])
MPD_CHECK_FLAG([-Wpointer-arith])
MPD_CHECK_FLAG([-Wstrict-prototypes])

@@ -173,7 +173,6 @@ cue_tag_file( FILE* fp,
{
struct tag* cd_tag = NULL;
struct tag* track_tag = NULL;
struct tag* merge_tag = NULL;
struct Cd* cd = NULL;
if (tnum > 256)
@@ -199,26 +198,7 @@ cue_tag_file( FILE* fp,
cd_delete(cd);
}
if ((cd_tag != NULL) && (track_tag != NULL))
{
merge_tag = tag_merge(cd_tag, track_tag);
tag_free(cd_tag);
tag_free(track_tag);
return merge_tag;
}
else if (cd_tag != NULL)
{
return cd_tag;
}
else if (track_tag != NULL)
{
return track_tag;
}
else
return NULL;
return tag_merge_replace(cd_tag, track_tag);
}
struct tag*

@@ -234,6 +234,7 @@ visitTag(struct client *client, struct strset *set,
struct song *song, enum tag_type tagType)
{
struct tag *tag = song->tag;
bool found = false;
if (tagType == LOCATE_TAG_FILE_TYPE) {
song_print_url(client, song);
@@ -246,10 +247,12 @@ visitTag(struct client *client, struct strset *set,
for (unsigned i = 0; i < tag->num_items; i++) {
if (tag->items[i]->type == tagType) {
strset_add(set, tag->items[i]->value);
found = true;
}
}
strset_add(set, "");
if (!found)
strset_add(set, "");
}
struct list_tags_data {

@@ -55,7 +55,7 @@ struct ffmpeg_context {
struct ffmpeg_stream {
/** hack - see url_to_struct() */
char url[8];
char url[64];
struct decoder *decoder;
struct input_stream *input;
@@ -140,8 +140,28 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
return -1;
}
/**
* Append the suffix of the original URI to the virtual stream URI.
* Without this, libavformat cannot detect some of the codecs
* (e.g. "shorten").
*/
static void
append_uri_suffix(struct ffmpeg_stream *stream, const char *uri)
{
assert(stream != NULL);
assert(uri != NULL);
char *base = g_path_get_basename(uri);
const char *suffix = strrchr(base, '.');
if (suffix != NULL && suffix[1] != 0)
g_strlcat(stream->url, suffix, sizeof(stream->url));
g_free(base);
}
static bool
ffmpeg_helper(struct input_stream *input,
ffmpeg_helper(const char *uri, struct input_stream *input,
bool (*callback)(struct ffmpeg_context *ctx),
struct ffmpeg_context *ctx)
{
@@ -154,6 +174,9 @@ ffmpeg_helper(struct input_stream *input,
};
bool ret;
if (uri != NULL)
append_uri_suffix(&stream, uri);
stream.input = input;
if (ctx && ctx->decoder) {
stream.decoder = ctx->decoder; //are we in decoding loop ?
@@ -349,7 +372,8 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
ctx.input = input;
ctx.decoder = decoder;
ffmpeg_helper(input, ffmpeg_decode_internal, &ctx);
ffmpeg_helper(decoder_get_uri(decoder), input,
ffmpeg_decode_internal, &ctx);
}
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
@@ -426,7 +450,7 @@ static struct tag *ffmpeg_tag(const char *file)
ctx.decoder = NULL;
ctx.tag = tag_new();
ret = ffmpeg_helper(&input, ffmpeg_tag_internal, &ctx);
ret = ffmpeg_helper(file, &input, ffmpeg_tag_internal, &ctx);
if (!ret) {
tag_free(ctx.tag);
ctx.tag = NULL;
@@ -462,25 +486,31 @@ static const char *const ffmpeg_suffixes[] = {
};
static const char *const ffmpeg_mime_types[] = {
"application/m4a",
"application/mp4",
"application/octet-stream",
"application/ogg",
"application/x-ms-wmz",
"application/x-ms-wmd",
"application/x-ogg",
"application/x-shockwave-flash",
"application/x-shorten",
"audio/8svx",
"audio/16sv",
"audio/aac",
"audio/ac3",
"audio/aiff"
"audio/amr",
"audio/basic",
"audio/flac",
"audio/m4a",
"audio/mp4",
"audio/mpeg",
"audio/musepack",
"audio/ogg",
"audio/qcelp",
"audio/vorbis",
"audio/vorbis+ogg",
"audio/x-8svx",
"audio/x-16sv",
"audio/x-aac",
@@ -493,15 +523,20 @@ static const char *const ffmpeg_mime_types[] = {
"audio/x-flac",
"audio/x-gsm",
"audio/x-mace",
"audio/x-matroska",
"audio/x-monkeys-audio",
"audio/x-mpeg",
"audio/x-ms-wma",
"audio/x-ms-wax",
"audio/x-musepack",
"audio/x-ogg",
"audio/x-vorbis",
"audio/x-vorbis+ogg",
"audio/x-pn-realaudio",
"audio/x-pn-multirate-realaudio",
"audio/x-speex",
"audio/x-tta"
"audio/x-voc",
"audio/x-wav",
"audio/x-wma",
"audio/x-wv",

@@ -871,9 +871,11 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
static const char *const oggflac_mime_types[] = {
"audio/x-flac+ogg",
"application/ogg",
"application/x-ogg",
"audio/ogg",
"audio/x-flac+ogg",
"audio/x-ogg",
NULL
};
@@ -894,7 +896,11 @@ const struct decoder_plugin oggflac_decoder_plugin = {
static const char *const flac_suffixes[] = { "flac", NULL };
static const char *const flac_mime_types[] = {
"audio/x-flac", "application/x-flac", NULL
"application/flac",
"application/x-flac",
"audio/flac",
"audio/x-flac",
NULL
};
const struct decoder_plugin flac_decoder_plugin = {

@@ -425,7 +425,27 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
/* This code is enabled when libid3tag is disabled. Instead
of parsing the ID3 frame, it just skips it. */
mad_stream_skip(&data->stream, tagsize);
size_t count = data->stream.bufend - data->stream.this_frame;
if (tagsize <= count) {
mad_stream_skip(&data->stream, tagsize);
} else {
mad_stream_skip(&data->stream, count);
while (count < tagsize) {
size_t len = tagsize - count;
char ignored[1024];
if (len > sizeof(ignored))
len = sizeof(ignored);
len = decoder_read(data->decoder, data->input_stream,
ignored, len);
if (len == 0)
break;
else
count += len;
}
}
#endif
}
@@ -433,16 +453,16 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
/**
* This function emulates libid3tag when it is disabled. Instead of
* doing a real analyzation of the frame, it just checks whether the
* frame begins with the string "ID3". If so, it returns the full
* length.
* frame begins with the string "ID3". If so, it returns the length
* of the ID3 frame.
*/
static signed long
id3_tag_query(const void *p0, size_t length)
{
const char *p = p0;
return length > 3 && memcmp(p, "ID3", 3) == 0
? length
return length >= 10 && memcmp(p, "ID3", 3) == 0
? (p[8] << 7) + p[9] + 10
: 0;
}
#endif /* !HAVE_ID3TAG */
@@ -1207,10 +1227,6 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
if (replay_gain_info)
replay_gain_info_free(replay_gain_info);
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK &&
data.mute_frame == MUTEFRAME_SEEK)
decoder_command_finished(decoder);
mp3_data_finish(&data);
}

@@ -24,6 +24,7 @@
#include <mpcdec/mpcdec.h>
#else
#include <mpc/mpcdec.h>
#include <math.h>
#endif
#include <glib.h>
@@ -103,7 +104,7 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
if (shift < 0)
val = sample << -shift;
val = sample >> -shift;
else
val = sample << shift;
#else
@@ -209,10 +210,17 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
}
replay_gain_info = replay_gain_info_new();
#ifdef MPC_IS_OLD_API
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01;
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0;
#else
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
#endif
decoder_initialized(mpd_decoder, &audio_format,
is->seekable,

@@ -357,9 +357,11 @@ fail:
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
static const char *const oggflac_mime_types[] = {
"audio/x-flac+ogg",
"application/ogg",
"application/x-ogg",
"audio/ogg",
"audio/x-ogg",
"audio/x-flac+ogg",
NULL
};

@@ -405,8 +405,13 @@ static const char *const vorbis_suffixes[] = {
static const char *const vorbis_mime_types[] = {
"application/ogg",
"audio/x-vorbis+ogg",
"application/x-ogg",
"audio/ogg",
"audio/vorbis",
"audio/vorbis+ogg",
"audio/x-ogg",
"audio/x-vorbis",
"audio/x-vorbis+ogg",
NULL
};

@@ -89,7 +89,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
struct input_stream input_stream;
const struct decoder_plugin *plugin;
if (!input_stream_open(&input_stream, uri)) {
close_instream = input_stream_open(&input_stream, uri);
if (!close_instream && !song_is_file(song)) {
dc.state = DECODE_STATE_ERROR;
return;
}
@@ -108,7 +109,7 @@ static void decoder_run_song(const struct song *song, const char *uri)
/* wait for the input stream to become ready; its metadata
will be available then */
while (!input_stream.ready) {
while (close_instream && !input_stream.ready) {
if (dc.command == DECODE_COMMAND_STOP) {
input_stream_close(&input_stream);
dc.state = DECODE_STATE_STOP;
@@ -124,7 +125,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
}
if (dc.command == DECODE_COMMAND_STOP) {
input_stream_close(&input_stream);
if (close_instream)
input_stream_close(&input_stream);
dc.state = DECODE_STATE_STOP;
return;
}
@@ -179,8 +181,11 @@ static void decoder_run_song(const struct song *song, const char *uri)
const char *s = uri_get_suffix(uri);
while ((plugin = decoder_plugin_from_suffix(s, next++))) {
if (plugin->file_decode != NULL) {
input_stream_close(&input_stream);
close_instream = false;
if (close_instream) {
input_stream_close(&input_stream);
close_instream = false;
}
ret = decoder_file_decode(plugin,
&decoder, uri);
if (ret)

@@ -41,9 +41,6 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_curl"
/** rewinding is possible after up to 64 kB */
static const off_t max_rewind_size = 64 * 1024;
/**
* Buffers created by input_curl_writefunction().
*/
@@ -78,9 +75,6 @@ struct input_curl {
/** did libcurl tell us the we're at the end of the response body? */
bool eof;
/** limited list of old buffers, for rewinding */
GQueue *rewind;
/** error message provided by libcurl */
char error[CURL_ERROR_SIZE];
@@ -176,11 +170,6 @@ input_curl_easy_free(struct input_curl *c)
g_queue_foreach(c->buffers, buffer_free_callback, NULL);
g_queue_clear(c->buffers);
if (c->rewind != NULL) {
g_queue_foreach(c->rewind, buffer_free_callback, NULL);
g_queue_clear(c->rewind);
}
}
/**
@@ -201,8 +190,6 @@ input_curl_free(struct input_stream *is)
curl_multi_cleanup(c->multi);
g_queue_free(c->buffers);
if (c->rewind != NULL)
g_queue_free(c->rewind);
g_free(c->url);
g_free(c);
@@ -322,7 +309,7 @@ fill_buffer(struct input_stream *is)
* Mark a part of the buffer object as consumed.
*/
static struct buffer *
consume_buffer(struct buffer *buffer, size_t length, GQueue *rewind_buffers)
consume_buffer(struct buffer *buffer, size_t length)
{
assert(buffer != NULL);
assert(buffer->consumed < buffer->size);
@@ -333,19 +320,14 @@ consume_buffer(struct buffer *buffer, size_t length, GQueue *rewind_buffers)
assert(buffer->consumed == buffer->size);
if (rewind_buffers != NULL)
/* append this buffer to the rewind buffer list */
g_queue_push_tail(rewind_buffers, buffer);
else
g_free(buffer);
g_free(buffer);
return NULL;
}
static size_t
read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
void *dest0, size_t length,
GQueue *rewind_buffers)
void *dest0, size_t length)
{
struct buffer *buffer = g_queue_pop_head(buffers);
uint8_t *dest = dest0;
@@ -364,7 +346,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
if (chunk > 0) {
memcpy(dest, buffer->data + buffer->consumed,
chunk);
buffer = consume_buffer(buffer, chunk, rewind_buffers);
buffer = consume_buffer(buffer, chunk);
nbytes += chunk;
dest += chunk;
@@ -379,7 +361,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed,
length);
if (chunk > 0) {
buffer = consume_buffer(buffer, chunk, rewind_buffers);
buffer = consume_buffer(buffer, chunk);
length -= chunk;
@@ -418,31 +400,9 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
{
struct input_curl *c = is->data;
bool success;
GQueue *rewind_buffers;
size_t nbytes = 0;
char *dest = ptr;
#ifndef NDEBUG
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0)) {
off_t offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
list != NULL; list = g_list_next(list)) {
buffer = list->data;
offset += buffer->consumed;
assert(offset <= is->offset);
}
buffer = g_queue_peek_head(c->buffers);
if (buffer != NULL)
offset += buffer->consumed;
assert(offset == is->offset);
}
#endif
do {
/* fill the buffer */
@@ -452,19 +412,9 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
/* send buffer contents */
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0))
/* at the beginning or already writing the rewind
buffer list */
rewind_buffers = c->rewind;
else
/* we don't need the rewind buffers anymore */
rewind_buffers = NULL;
while (size > 0 && !g_queue_is_empty(c->buffers)) {
size_t copy = read_from_buffer(&c->icy_metadata, c->buffers,
dest + nbytes, size,
rewind_buffers);
dest + nbytes, size);
nbytes += copy;
size -= copy;
@@ -476,33 +426,6 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
is->offset += (off_t)nbytes;
#ifndef NDEBUG
if (rewind_buffers != NULL) {
off_t offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
list != NULL; list = g_list_next(list)) {
buffer = list->data;
offset += buffer->consumed;
assert(offset <= is->offset);
}
buffer = g_queue_peek_head(c->buffers);
if (buffer != NULL)
offset += buffer->consumed;
assert(offset == is->offset);
}
#endif
if (rewind_buffers != NULL && is->offset > max_rewind_size) {
/* drop the rewind buffer, it has grown too large */
g_queue_foreach(c->rewind, buffer_free_callback, NULL);
g_queue_clear(c->rewind);
}
return nbytes;
}
@@ -635,15 +558,6 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
/* a stream with icy-metadata is not
seekable */
is->seekable = false;
if (c->rewind != NULL) {
/* rewinding with icy-metadata is too
hairy for me .. */
assert(g_queue_is_empty(c->rewind));
g_queue_free(c->rewind);
c->rewind = NULL;
}
}
}
@@ -732,6 +646,18 @@ input_curl_easy_init(struct input_stream *is)
return true;
}
void
input_curl_reinit(struct input_stream *is)
{
struct input_curl *c = is->data;
assert(is->plugin == &input_plugin_curl);
assert(c->easy != NULL);
curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is);
curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is);
}
static bool
input_curl_send_request(struct input_curl *c)
{
@@ -751,72 +677,6 @@ input_curl_send_request(struct input_curl *c)
return true;
}
static bool
input_curl_can_rewind(struct input_stream *is)
{
struct input_curl *c = is->data;
struct buffer *buffer;
if (c->rewind == NULL)
return false;
if (!g_queue_is_empty(c->rewind))
/* the rewind buffer hasn't been wiped yet */
return true;
if (g_queue_is_empty(c->buffers))
/* there are no buffers at all - cheap rewind not
possible */
return false;
/* rewind is possible if this is the very first buffer of the
resource */
buffer = (struct buffer*)g_queue_peek_head(c->buffers);
return (off_t)buffer->consumed == is->offset;
}
static void
input_curl_rewind(struct input_stream *is)
{
struct input_curl *c = is->data;
#ifndef NDEBUG
off_t offset = 0;
#endif
assert(c->rewind != NULL);
/* rewind the current buffer */
if (!g_queue_is_empty(c->buffers)) {
struct buffer *buffer =
(struct buffer*)g_queue_peek_head(c->buffers);
#ifndef NDEBUG
offset += buffer->consumed;
#endif
buffer->consumed = 0;
}
/* reset and move all rewind buffers back to the regular buffer list */
while (!g_queue_is_empty(c->rewind)) {
struct buffer *buffer =
(struct buffer*)g_queue_pop_tail(c->rewind);
#ifndef NDEBUG
offset += buffer->consumed;
#endif
buffer->consumed = 0;
g_queue_push_head(c->buffers, buffer);
}
assert(offset == is->offset);
is->offset = 0;
/* rewind the icy_metadata object */
icy_reset(&c->icy_metadata);
}
static bool
input_curl_seek(struct input_stream *is, off_t offset, int whence)
{
@@ -825,17 +685,9 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
assert(is->ready);
if (whence == SEEK_SET && offset == 0) {
if (is->offset == 0)
/* no-op */
return true;
if (input_curl_can_rewind(is)) {
/* we have enough rewind buffers left */
input_curl_rewind(is);
return true;
}
}
if (whence == SEEK_SET && offset == is->offset)
/* no-op */
return true;
if (!is->seekable)
return false;
@@ -868,26 +720,16 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
/* check if we can fast-forward the buffer */
while (offset > is->offset && !g_queue_is_empty(c->buffers)) {
GQueue *rewind_buffers;
struct buffer *buffer;
size_t length;
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0))
/* at the beginning or already writing the rewind
buffer list */
rewind_buffers = c->rewind;
else
/* we don't need the rewind buffers anymore */
rewind_buffers = NULL;
buffer = (struct buffer *)g_queue_pop_head(c->buffers);
length = buffer->size - buffer->consumed;
if (offset - is->offset < (off_t)length)
length = offset - is->offset;
buffer = consume_buffer(buffer, length, rewind_buffers);
buffer = consume_buffer(buffer, length);
if (buffer != NULL)
g_queue_push_head(c->buffers, buffer);
@@ -940,7 +782,6 @@ input_curl_open(struct input_stream *is, const char *url)
c = g_new0(struct input_curl, 1);
c->url = g_strdup(url);
c->buffers = g_queue_new();
c->rewind = g_queue_new();
is->plugin = &input_plugin_curl;
is->data = c;

@@ -20,6 +20,16 @@
#ifndef MPD_INPUT_CURL_H
#define MPD_INPUT_CURL_H
struct input_stream;
extern const struct input_plugin input_plugin_curl;
/**
* This is a workaround for an input_stream API deficiency; after
* exchanging the input_stream pointer in input_rewind_open(), this
* function is called to reinitialize CURL's data pointers.
*/
void
input_curl_reinit(struct input_stream *is);
#endif

@@ -49,10 +49,13 @@ input_mms_open(struct input_stream *is, const char *url)
m = g_new(struct input_mms, 1);
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
if (m->mms == NULL) {
g_free(m);
g_warning("mmsx_connect() failed");
return false;
}
m->eof = false;
/* XX is this correct? at least this selects the ffmpeg
decoder, which seems to work fine*/
is->mime = g_strdup("audio/x-ms-wma");

@@ -0,0 +1,238 @@
/*
* Copyright (C) 2003-2009 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 "input/rewind_input_plugin.h"
#include "input/curl_input_plugin.h"
#include "input_plugin.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
#include <stdio.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_rewind"
struct input_rewind {
struct input_stream input;
/**
* The read position within the buffer. Undefined as long as
* reading_from_buffer() returns false.
*/
size_t head;
/**
* The write/append position within the buffer.
*/
size_t tail;
/**
* The size of this buffer is the maximum number of bytes
* which can be rewinded cheaply without passing the "seek"
* call to CURL.
*
* The origin of this buffer is always the beginning of the
* stream (offset 0).
*/
char buffer[64 * 1024];
};
/**
* Are we currently reading from the buffer, and does the buffer
* contain more data for the next read operation?
*/
static bool
reading_from_buffer(const struct input_stream *is)
{
const struct input_rewind *r = is->data;
return r->tail > 0 && is->offset < r->input.offset;
}
/**
* Copy public attributes from the underlying input stream to the
* "rewind" input stream. This function is called when a method of
* the underlying stream has returned, which may have modified these
* attributes.
*/
static void
copy_attributes(struct input_stream *dest)
{
const struct input_rewind *r = dest->data;
const struct input_stream *src = &r->input;
dest->ready = src->ready;
dest->seekable = src->seekable;
dest->error = src->error;
dest->size = src->size;
dest->offset = src->offset;
if (dest->mime == NULL && src->mime != NULL)
/* this is set only once, and the duplicated pointer
is freed by input_stream_close() */
dest->mime = g_strdup(src->mime);
}
static void
input_rewind_close(struct input_stream *is)
{
struct input_rewind *r = is->data;
input_stream_close(&r->input);
g_free(r);
}
static struct tag *
input_rewind_tag(struct input_stream *is)
{
struct input_rewind *r = is->data;
return input_stream_tag(&r->input);
}
static int
input_rewind_buffer(struct input_stream *is)
{
struct input_rewind *r = is->data;
int ret = input_stream_buffer(&r->input);
if (ret < 0 || !reading_from_buffer(is))
copy_attributes(is);
return ret;
}
static size_t
input_rewind_read(struct input_stream *is, void *ptr, size_t size)
{
struct input_rewind *r = is->data;
if (reading_from_buffer(is)) {
/* buffered read */
assert(r->head == (size_t)is->offset);
assert(r->tail == (size_t)r->input.offset);
if (size > r->tail - r->head)
size = r->tail - r->head;
memcpy(ptr, r->buffer + r->head, size);
r->head += size;
is->offset += size;
return size;
} else {
/* pass method call to underlying stream */
size_t nbytes = input_stream_read(&r->input, ptr, size);
if (r->input.offset > (off_t)sizeof(r->buffer))
/* disable buffering */
r->tail = 0;
else if (r->tail == (size_t)is->offset) {
/* append to buffer */
memcpy(r->buffer + r->tail, ptr, nbytes);
r->tail += nbytes;
assert(r->tail == (size_t)r->input.offset);
}
copy_attributes(is);
return nbytes;
}
}
static bool
input_rewind_eof(G_GNUC_UNUSED struct input_stream *is)
{
struct input_rewind *r = is->data;
return !reading_from_buffer(is) && input_stream_eof(&r->input);
}
static bool
input_rewind_seek(struct input_stream *is, off_t offset, int whence)
{
struct input_rewind *r = is->data;
assert(is->ready);
if (whence == SEEK_SET && r->tail > 0 && offset <= (off_t)r->tail) {
/* buffered seek */
assert(!reading_from_buffer(is) ||
r->head == (size_t)is->offset);
assert(r->tail == (size_t)r->input.offset);
r->head = (size_t)offset;
is->offset = offset;
return true;
} else {
bool success = input_stream_seek(&r->input, offset, whence);
copy_attributes(is);
/* disable the buffer, because r->input has left the
buffered range now */
r->tail = 0;
return success;
}
}
static const struct input_plugin rewind_input_plugin = {
.close = input_rewind_close,
.tag = input_rewind_tag,
.buffer = input_rewind_buffer,
.read = input_rewind_read,
.eof = input_rewind_eof,
.seek = input_rewind_seek,
};
void
input_rewind_open(struct input_stream *is)
{
struct input_rewind *c;
assert(is != NULL);
assert(is->offset == 0);
if (is->plugin != &input_plugin_curl)
/* due to limitations in the input_plugin API, we only
(explicitly) support the CURL input plugin */
return;
c = g_new(struct input_rewind, 1);
c->tail = 0;
/* move the CURL input stream to c->input */
c->input = *is;
input_curl_reinit(&c->input);
/* convert the existing input_stream pointer to a "rewind"
input stream */
is->plugin = &rewind_input_plugin;
is->data = c;
}

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2003-2009 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.
*/
/** \file
*
* A wrapper for an input_stream object which allows cheap buffered
* rewinding. This is useful while detecting the stream codec (let
* each decoder plugin peek a portion from the stream).
*/
#ifndef MPD_INPUT_REWIND_H
#define MPD_INPUT_REWIND_H
#include "config.h"
struct input_stream;
#ifdef HAVE_CURL
void
input_rewind_open(struct input_stream *is);
#else
static inline void
input_rewind_open(struct input_stream *is)
{
(void)is;
}
#endif
#endif

@@ -22,6 +22,7 @@
#include "conf.h"
#include "input/file_input_plugin.h"
#include "input/rewind_input_plugin.h"
#ifdef ENABLE_ARCHIVE
#include "input/archive_input_plugin.h"
@@ -131,6 +132,8 @@ input_stream_open(struct input_stream *is, const char *url)
assert(is->plugin->eof != NULL);
assert(!is->seekable || is->plugin->seek != NULL);
input_rewind_open(is);
return true;
}
}

@@ -89,6 +89,13 @@ struct player {
*/
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.
*/
struct tag *cross_fade_tag;
/**
* The current audio format for the audio outputs.
*/
@@ -518,6 +525,14 @@ play_next_chunk(struct player *player)
chunk = music_pipe_shift(player->pipe);
assert(chunk != NULL);
/* don't send the tags of the new song (which
is being faded in) yet; postpone it until
the current song is faded out */
player->cross_fade_tag =
tag_merge_replace(player->cross_fade_tag,
other_chunk->tag);
other_chunk->tag = NULL;
cross_fade_apply(chunk, other_chunk,
&dc.out_audio_format,
cross_fade_position,
@@ -544,6 +559,14 @@ play_next_chunk(struct player *player)
assert(chunk != NULL);
/* insert the postponed tag if cross-fading is finished */
if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) {
chunk->tag = tag_merge_replace(chunk->tag,
player->cross_fade_tag);
player->cross_fade_tag = NULL;
}
/* play the current chunk */
success = play_chunk(player->song, chunk, &player->play_audio_format,
@@ -608,6 +631,7 @@ static void do_play(void)
.xfade = XFADE_UNKNOWN,
.cross_fading = false,
.cross_fade_chunks = 0,
.cross_fade_tag = NULL,
.size_to_time = 0.0,
};
@@ -754,6 +778,9 @@ static void do_play(void)
music_pipe_clear(player.pipe, player_buffer);
music_pipe_free(player.pipe);
if (player.cross_fade_tag != NULL)
tag_free(player.cross_fade_tag);
pc.state = PLAYER_STATE_STOP;
event_pipe_emit(PIPE_EVENT_PLAYLIST);
}

@@ -140,7 +140,8 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
? queue_next_order(&playlist->queue, playlist->current)
: 0;
if (next_order == 0 && playlist->queue.random) {
if (next_order == 0 && playlist->queue.random &&
!playlist->queue.single) {
/* shuffle the song order again, so we get a different
order each time the playlist is played
completely */

@@ -45,14 +45,14 @@ queue_next_order(const struct queue *queue, unsigned order)
if (queue->single)
{
if (queue->repeat)
if (queue->repeat && !queue->consume)
return order;
else
return -1;
}
if (order + 1 < queue->length)
return order + 1;
else if (queue->repeat)
else if (queue->repeat && (order > 0 || !queue->consume))
/* restart at first song */
return 0;
else

@@ -247,6 +247,22 @@ tag_merge(const struct tag *base, const struct tag *add)
return ret;
}
struct tag *
tag_merge_replace(struct tag *base, struct tag *add)
{
if (add == NULL)
return base;
if (base == NULL)
return add;
struct tag *tag = tag_merge(base, add);
tag_free(base);
tag_free(add);
return tag;
}
const char *
tag_get_value(const struct tag *tag, enum tag_type type)
{

@@ -165,6 +165,15 @@ struct tag *tag_dup(const struct tag *tag);
struct tag *
tag_merge(const struct tag *base, const struct tag *add);
/**
* Merges the data from two tags. Any of the two may be NULL. Both
* are freed by this function.
*
* @return a newly allocated tag, which must be freed with tag_free()
*/
struct tag *
tag_merge_replace(struct tag *base, struct tag *add);
/**
* Returns true if the tag contains no items. This ignores the "time"
* attribute.