aboutsummaryrefslogtreecommitdiffstats
path: root/src/playlist
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/playlist.c77
-rw-r--r--src/playlist.h110
-rw-r--r--src/playlist/asx_playlist_plugin.c322
-rw-r--r--src/playlist/asx_playlist_plugin.h25
-rw-r--r--src/playlist/cue_playlist_plugin.c131
-rw-r--r--src/playlist/cue_playlist_plugin.h25
-rw-r--r--src/playlist/extm3u_playlist_plugin.c161
-rw-r--r--src/playlist/extm3u_playlist_plugin.h25
-rw-r--r--src/playlist/lastfm_playlist_plugin.c314
-rw-r--r--src/playlist/lastfm_playlist_plugin.h25
-rw-r--r--src/playlist/m3u_playlist_plugin.c92
-rw-r--r--src/playlist/m3u_playlist_plugin.h25
-rw-r--r--src/playlist/pls_playlist_plugin.c219
-rw-r--r--src/playlist/pls_playlist_plugin.h25
-rw-r--r--src/playlist/xspf_playlist_plugin.c342
-rw-r--r--src/playlist/xspf_playlist_plugin.h25
-rw-r--r--src/playlist_control.c81
-rw-r--r--src/playlist_edit.c167
-rw-r--r--src/playlist_global.c19
-rw-r--r--src/playlist_internal.h2
-rw-r--r--src/playlist_list.c332
-rw-r--r--src/playlist_list.h65
-rw-r--r--src/playlist_plugin.h137
-rw-r--r--src/playlist_print.c3
-rw-r--r--src/playlist_queue.c283
-rw-r--r--src/playlist_queue.h51
-rw-r--r--src/playlist_save.c7
-rw-r--r--src/playlist_state.c112
-rw-r--r--src/playlist_state.h14
29 files changed, 2963 insertions, 253 deletions
diff --git a/src/playlist.c b/src/playlist.c
index 35c09329a..691fe5d26 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_internal.h"
#include "playlist_save.h"
#include "player_control.h"
@@ -34,7 +35,8 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-void playlistVersionChange(struct playlist *playlist)
+void
+playlist_increment_version_all(struct playlist *playlist)
{
queue_modify_all(&playlist->queue);
idle_add(IDLE_PLAYLIST);
@@ -61,16 +63,12 @@ playlist_init(struct playlist *playlist)
playlist->queued = -1;
playlist->current = -1;
-
- playlist->prev_elapsed = g_timer_new();
}
void
playlist_finish(struct playlist *playlist)
{
queue_finish(&playlist->queue);
-
- g_timer_destroy(playlist->prev_elapsed);
}
/**
@@ -91,14 +89,15 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order)
g_debug("queue song %i:\"%s\"", playlist->queued, uri);
g_free(uri);
- queueSong(song);
+ pc_enqueue_song(song);
}
/**
* Check if the player thread has already started playing the "queued"
* song.
*/
-static void syncPlaylistWithQueue(struct playlist *playlist)
+static void
+playlist_sync_with_queue(struct playlist *playlist)
{
if (pc.next_song == NULL && playlist->queued != -1) {
/* queued song has started: copy queued to current,
@@ -109,7 +108,7 @@ static void syncPlaylistWithQueue(struct playlist *playlist)
playlist->queued = -1;
if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
idle_add(IDLE_PLAYER);
}
@@ -178,7 +177,7 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
}
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
+playlist_play_order(struct playlist *playlist, int orderNum)
{
struct song *song;
char *uri;
@@ -192,34 +191,35 @@ playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
g_debug("play %i:\"%s\"", orderNum, uri);
g_free(uri);
- playerPlay(song);
+ pc_play(song);
playlist->current = orderNum;
}
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist);
+playlist_resume_playback(struct playlist *playlist);
/**
* This is the "PLAYLIST" event handler. It is invoked by the player
* thread whenever it requests a new queued song, or when it exits.
*/
-void syncPlayerAndPlaylist(struct playlist *playlist)
+void
+playlist_sync(struct playlist *playlist)
{
if (!playlist->playing)
/* this event has reached us out of sync: we aren't
playing anymore; ignore the event */
return;
- if (getPlayerState() == PLAYER_STATE_STOP)
+ if (pc_get_state() == PLAYER_STATE_STOP)
/* the player thread has stopped: check if playback
should be restarted with the next song. That can
happen if the playlist isn't filling the queue fast
enough */
- playPlaylistIfPlayerStopped(playlist);
+ playlist_resume_playback(playlist);
else {
/* check if the player thread has already started
playing the queued song */
- syncPlaylistWithQueue(playlist);
+ playlist_sync_with_queue(playlist);
/* make sure the queued song is always set (if
possible) */
@@ -233,14 +233,14 @@ void syncPlayerAndPlaylist(struct playlist *playlist)
* decide whether to re-start playback
*/
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist)
+playlist_resume_playback(struct playlist *playlist)
{
enum player_error error;
assert(playlist->playing);
- assert(getPlayerState() == PLAYER_STATE_STOP);
+ assert(pc_get_state() == PLAYER_STATE_STOP);
- error = getPlayerError();
+ error = pc_get_error();
if (error == PLAYER_ERROR_NOERROR)
playlist->error_count = 0;
else
@@ -251,37 +251,38 @@ playPlaylistIfPlayerStopped(struct playlist *playlist)
playlist->error_count >= queue_length(&playlist->queue))
/* too many errors, or critical error: stop
playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
else
/* continue playback at the next song */
- nextSongInPlaylist(playlist);
+ playlist_next(playlist);
}
bool
-getPlaylistRepeatStatus(const struct playlist *playlist)
+playlist_get_repeat(const struct playlist *playlist)
{
return playlist->queue.repeat;
}
bool
-getPlaylistRandomStatus(const struct playlist *playlist)
+playlist_get_random(const struct playlist *playlist)
{
return playlist->queue.random;
}
bool
-getPlaylistSingleStatus(const struct playlist *playlist)
+playlist_get_single(const struct playlist *playlist)
{
return playlist->queue.single;
}
bool
-getPlaylistConsumeStatus(const struct playlist *playlist)
+playlist_get_consume(const struct playlist *playlist)
{
return playlist->queue.consume;
}
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
+void
+playlist_set_repeat(struct playlist *playlist, bool status)
{
if (status == playlist->queue.repeat)
return;
@@ -296,7 +297,8 @@ void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-static void orderPlaylist(struct playlist *playlist)
+static void
+playlist_order(struct playlist *playlist)
{
if (playlist->current >= 0)
/* update playlist.current, order==position now */
@@ -306,7 +308,8 @@ static void orderPlaylist(struct playlist *playlist)
queue_restore_order(&playlist->queue);
}
-void setPlaylistSingleStatus(struct playlist *playlist, bool status)
+void
+playlist_set_single(struct playlist *playlist, bool status)
{
if (status == playlist->queue.single)
return;
@@ -321,7 +324,8 @@ void setPlaylistSingleStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
+void
+playlist_set_consume(struct playlist *playlist, bool status)
{
if (status == playlist->queue.consume)
return;
@@ -330,7 +334,8 @@ void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistRandomStatus(struct playlist *playlist, bool status)
+void
+playlist_set_random(struct playlist *playlist, bool status)
{
const struct song *queued;
@@ -365,14 +370,15 @@ void setPlaylistRandomStatus(struct playlist *playlist, bool status)
} else
playlist->current = -1;
} else
- orderPlaylist(playlist);
+ playlist_order(playlist);
playlist_update_queued_song(playlist, queued);
idle_add(IDLE_OPTIONS);
}
-int getPlaylistCurrentSong(const struct playlist *playlist)
+int
+playlist_get_current_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
return queue_order_to_position(&playlist->queue,
@@ -381,7 +387,8 @@ int getPlaylistCurrentSong(const struct playlist *playlist)
return -1;
}
-int getPlaylistNextSong(const struct playlist *playlist)
+int
+playlist_get_next_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
{
@@ -404,19 +411,19 @@ int getPlaylistNextSong(const struct playlist *playlist)
}
unsigned long
-getPlaylistVersion(const struct playlist *playlist)
+playlist_get_version(const struct playlist *playlist)
{
return playlist->queue.version;
}
int
-getPlaylistLength(const struct playlist *playlist)
+playlist_get_length(const struct playlist *playlist)
{
return queue_length(&playlist->queue);
}
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song)
+playlist_get_song_id(const struct playlist *playlist, unsigned song)
{
return queue_position_to_id(&playlist->queue, song);
}
diff --git a/src/playlist.h b/src/playlist.h
index 57b2450fa..f8f5bd942 100644
--- a/src/playlist.h
+++ b/src/playlist.h
@@ -23,7 +23,6 @@
#include "queue.h"
#include <stdbool.h>
-#include <stdio.h>
#define PLAYLIST_COMMENT '#'
@@ -82,21 +81,16 @@ struct playlist {
* This variable is only valid if #playing is true.
*/
int queued;
-
- /**
- * This timer tracks the time elapsed since the last "prev"
- * command. If that is less than one second ago, "prev" jumps
- * to the previous song instead of rewinding the current song.
- */
- GTimer *prev_elapsed;
};
/** the global playlist object */
extern struct playlist g_playlist;
-void initPlaylist(void);
+void
+playlist_global_init(void);
-void finishPlaylist(void);
+void
+playlist_global_finish(void);
void
playlist_init(struct playlist *playlist);
@@ -116,11 +110,8 @@ playlist_get_queue(const struct playlist *playlist)
return &playlist->queue;
}
-void readPlaylistState(FILE *);
-
-void savePlaylistState(FILE *);
-
-void clearPlaylist(struct playlist *playlist);
+void
+playlist_clear(struct playlist *playlist);
#ifndef WIN32
/**
@@ -133,90 +124,111 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
#endif
enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id);
+playlist_append_uri(struct playlist *playlist, const char *file,
+ unsigned *added_id);
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id);
enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song);
+playlist_delete(struct playlist *playlist, unsigned song);
+
+/**
+ * Deletes a range of songs from the playlist.
+ *
+ * @param start the position of the first song to delete
+ * @param end the position after the last song to delete
+ */
+enum playlist_result
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end);
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned song);
+playlist_delete_id(struct playlist *playlist, unsigned song);
-void stopPlaylist(struct playlist *playlist);
+void
+playlist_stop(struct playlist *playlist);
enum playlist_result
-playPlaylist(struct playlist *playlist, int song);
+playlist_play(struct playlist *playlist, int song);
enum playlist_result
-playPlaylistById(struct playlist *playlist, int song);
+playlist_play_id(struct playlist *playlist, int song);
-void nextSongInPlaylist(struct playlist *playlist);
+void
+playlist_next(struct playlist *playlist);
-void syncPlayerAndPlaylist(struct playlist *playlist);
+void
+playlist_sync(struct playlist *playlist);
-void previousSongInPlaylist(struct playlist *playlist);
+void
+playlist_previous(struct playlist *playlist);
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end);
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end);
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song);
+playlist_delete_song(struct playlist *playlist, const struct song *song);
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to);
+playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to);
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to);
+playlist_move_id(struct playlist *playlist, unsigned id, int to);
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2);
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2);
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2);
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2);
bool
-getPlaylistRepeatStatus(const struct playlist *playlist);
+playlist_get_repeat(const struct playlist *playlist);
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status);
+void
+playlist_set_repeat(struct playlist *playlist, bool status);
bool
-getPlaylistRandomStatus(const struct playlist *playlist);
+playlist_get_random(const struct playlist *playlist);
-void setPlaylistRandomStatus(struct playlist *playlist, bool status);
+void
+playlist_set_random(struct playlist *playlist, bool status);
bool
-getPlaylistSingleStatus(const struct playlist *playlist);
+playlist_get_single(const struct playlist *playlist);
-void setPlaylistSingleStatus(struct playlist *playlist, bool status);
+void
+playlist_set_single(struct playlist *playlist, bool status);
bool
-getPlaylistConsumeStatus(const struct playlist *playlist);
+playlist_get_consume(const struct playlist *playlist);
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status);
+void
+playlist_set_consume(struct playlist *playlist, bool status);
-int getPlaylistCurrentSong(const struct playlist *playlist);
+int
+playlist_get_current_song(const struct playlist *playlist);
-int getPlaylistNextSong(const struct playlist *playlist);
+int
+playlist_get_next_song(const struct playlist *playlist);
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song);
+playlist_get_song_id(const struct playlist *playlist, unsigned song);
-int getPlaylistLength(const struct playlist *playlist);
+int
+playlist_get_length(const struct playlist *playlist);
unsigned long
-getPlaylistVersion(const struct playlist *playlist);
+playlist_get_version(const struct playlist *playlist);
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time);
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time);
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist,
+playlist_seek_song_id(struct playlist *playlist,
unsigned id, float seek_time);
-void playlistVersionChange(struct playlist *playlist);
-
-int is_valid_playlist_name(const char *utf8path);
+void
+playlist_increment_version_all(struct playlist *playlist);
#endif
diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c
new file mode 100644
index 000000000..03a5edea6
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.c
@@ -0,0 +1,322 @@
+/*
+ * 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 "playlist/asx_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "asx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct asx_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ENTRY,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static const gchar *
+get_attribute(const gchar **attribute_names, const gchar **attribute_values,
+ const gchar *name)
+{
+ for (unsigned i = 0; attribute_names[i] != NULL; ++i)
+ if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
+ return attribute_values[i];
+
+ return NULL;
+}
+
+static void
+asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ parser->state = ENTRY;
+ parser->song = song_remote_new("asx:");
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "ref") == 0) {
+ const gchar *href = get_attribute(attribute_names,
+ attribute_values,
+ "href");
+ if (href != NULL) {
+ /* create new song object, and copy
+ the existing tag over; we cannot
+ replace the existing song's URI,
+ because that attribute is
+ immutable */
+ struct song *song = song_remote_new(href);
+
+ if (parser->song != NULL) {
+ song->tag = parser->song->tag;
+ parser->song->tag = NULL;
+ song_free(parser->song);
+ }
+
+ parser->song = song;
+ }
+ } else if (g_ascii_strcasecmp(element_name, "author") == 0)
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag = TAG_ARTIST;
+ else if (g_ascii_strcasecmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void
+asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ if (strcmp(parser->song->uri, "asx:") != 0)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+ else
+ song_free(parser->song);
+
+ parser->state = ROOT;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void
+asx_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser asx_parser = {
+ .start_element = asx_start_element,
+ .end_element = asx_end_element,
+ .text = asx_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+asx_parser_destroy(gpointer data)
+{
+ struct asx_parser *parser = data;
+
+ if (parser->state >= ENTRY)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct asx_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+asx_open_stream(struct input_stream *is)
+{
+ struct asx_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct asx_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the ASX XML file */
+
+ context = g_markup_parse_context_new(&asx_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, asx_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #asx_playlist object from the parsed song list */
+
+ playlist = g_new(struct asx_playlist, 1);
+ playlist_provider_init(&playlist->base, &asx_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+asx_close(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+asx_read(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ NULL
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ NULL
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ .name = "asx",
+
+ .open_stream = asx_open_stream,
+ .close = asx_close,
+ .read = asx_read,
+
+ .suffixes = asx_suffixes,
+ .mime_types = asx_mime_types,
+};
diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h
new file mode 100644
index 000000000..021e97f56
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c
new file mode 100644
index 000000000..a9b596560
--- /dev/null
+++ b/src/playlist/cue_playlist_plugin.c
@@ -0,0 +1,131 @@
+/*
+ * 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 "playlist/cue_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "tag.h"
+#include "song.h"
+#include "cue/cue_tag.h"
+
+#include <glib.h>
+#include <libcue/libcue.h>
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cue"
+
+struct cue_playlist {
+ struct playlist_provider base;
+
+ struct Cd *cd;
+
+ unsigned next;
+};
+
+static struct playlist_provider *
+cue_playlist_open_uri(const char *uri)
+{
+ struct cue_playlist *playlist;
+ FILE *file;
+ struct Cd *cd;
+
+ file = fopen(uri, "rt");
+ if (file == NULL)
+ return NULL;
+
+ cd = cue_parse_file(file);
+ fclose(file);
+ if (cd == NULL)
+ return NULL;
+
+ playlist = g_new(struct cue_playlist, 1);
+ playlist_provider_init(&playlist->base, &cue_playlist_plugin);
+ playlist->cd = cd;
+ playlist->next = 1;
+
+ return &playlist->base;
+}
+
+static void
+cue_playlist_close(struct playlist_provider *_playlist)
+{
+ struct cue_playlist *playlist = (struct cue_playlist *)_playlist;
+
+ cd_delete(playlist->cd);
+ g_free(playlist);
+}
+
+static struct song *
+cue_playlist_read(struct playlist_provider *_playlist)
+{
+ struct cue_playlist *playlist = (struct cue_playlist *)_playlist;
+ struct Track *track;
+ struct tag *tag;
+ const char *filename;
+ struct song *song;
+
+ track = cd_get_track(playlist->cd, playlist->next);
+ if (track == NULL)
+ return NULL;
+
+ tag = cue_tag(playlist->cd, playlist->next);
+ if (tag == NULL)
+ return NULL;
+
+ ++playlist->next;
+
+ filename = track_get_filename(track);
+ if (*filename == 0 || filename[0] == '.' ||
+ strchr(filename, '/') != NULL) {
+ /* unsafe characters found, bail out */
+ tag_free(tag);
+ return NULL;
+ }
+
+ song = song_remote_new(filename);
+ song->tag = tag;
+ song->start_ms = (track_get_start(track) * 1000) / 75;
+ song->end_ms = ((track_get_start(track) + track_get_length(track))
+ * 1000) / 75;
+
+ return song;
+}
+
+static const char *const cue_playlist_suffixes[] = {
+ "cue",
+ NULL
+};
+
+static const char *const cue_playlist_mime_types[] = {
+ "application/x-cue",
+ NULL
+};
+
+const struct playlist_plugin cue_playlist_plugin = {
+ .name = "cue",
+
+ .open_uri = cue_playlist_open_uri,
+ .close = cue_playlist_close,
+ .read = cue_playlist_read,
+
+ .suffixes = cue_playlist_suffixes,
+ .mime_types = cue_playlist_mime_types,
+};
diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h
new file mode 100644
index 000000000..2b5319388
--- /dev/null
+++ b/src/playlist/cue_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin cue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c
new file mode 100644
index 000000000..bd81ff9fb
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.c
@@ -0,0 +1,161 @@
+/*
+ * 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 "playlist/extm3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+struct extm3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+extm3u_open_stream(struct input_stream *is)
+{
+ struct extm3u_playlist *playlist;
+ const char *line;
+
+ playlist = g_new(struct extm3u_playlist, 1);
+ playlist->tis = text_input_stream_new(is);
+
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL || strcmp(line, "#EXTM3U") != 0) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+ return NULL;
+ }
+
+ playlist_provider_init(&playlist->base, &extm3u_playlist_plugin);
+ return &playlist->base;
+}
+
+static void
+extm3u_close(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+/**
+ * Parse a EXTINF line.
+ *
+ * @param line the rest of the input line after the colon
+ */
+static struct tag *
+extm3u_parse_tag(const char *line)
+{
+ long duration;
+ char *endptr;
+ const char *name;
+ struct tag *tag;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return NULL;
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = g_strchug(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return NULL;
+
+ tag = tag_new();
+ tag->time = duration;
+
+ /* unfortunately, there is no real specification for the
+ EXTM3U format, so we must assume that the string after the
+ comma is opaque, and is just the song name*/
+ if (*name != 0)
+ tag_add_item(tag, TAG_NAME, name);
+
+ return tag;
+}
+
+static struct song *
+extm3u_read(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+ struct tag *tag = NULL;
+ const char *line;
+ struct song *song;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL) {
+ if (tag != NULL)
+ tag_free(tag);
+ return NULL;
+ }
+
+ if (g_str_has_prefix(line, "#EXTINF:")) {
+ if (tag != NULL)
+ tag_free(tag);
+ tag = extm3u_parse_tag(line + 8);
+ continue;
+ }
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ song = song_remote_new(line);
+ song->tag = tag;
+ return song;
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ .name = "extm3u",
+
+ .open_stream = extm3u_open_stream,
+ .close = extm3u_close,
+ .read = extm3u_read,
+
+ .suffixes = extm3u_suffixes,
+ .mime_types = extm3u_mime_types,
+};
diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h
new file mode 100644
index 000000000..07f18eafa
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c
new file mode 100644
index 000000000..ec499925a
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.c
@@ -0,0 +1,314 @@
+/*
+ * 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 "playlist/lastfm_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "playlist_list.h"
+#include "conf.h"
+#include "uri.h"
+#include "song.h"
+#include "input_stream.h"
+#include "glib_compat.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct lastfm_playlist {
+ struct playlist_provider base;
+
+ struct input_stream is;
+
+ struct playlist_provider *xspf;
+};
+
+static struct {
+ char *user;
+ char *md5;
+} lastfm_config;
+
+static bool
+lastfm_init(const struct config_param *param)
+{
+ const char *user = config_get_block_string(param, "user", NULL);
+ const char *passwd = config_get_block_string(param, "password", NULL);
+
+ if (user == NULL || passwd == NULL) {
+ g_debug("disabling the last.fm playlist plugin "
+ "because account is not configured");
+ return false;
+ }
+
+ lastfm_config.user = g_uri_escape_string(user, NULL, false);
+
+#if GLIB_CHECK_VERSION(2,16,0)
+ if (strlen(passwd) != 32)
+ lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
+ passwd, strlen(passwd));
+ else
+#endif
+ lastfm_config.md5 = g_strdup(passwd);
+
+ return true;
+}
+
+static void
+lastfm_finish(void)
+{
+ g_free(lastfm_config.user);
+ g_free(lastfm_config.md5);
+}
+
+/**
+ * Simple data fetcher.
+ * @param url path or url of data to fetch.
+ * @return data fetched, or NULL on error. Must be freed with g_free.
+ */
+static char *
+lastfm_get(const char *url)
+{
+ struct input_stream input_stream;
+ GError *error = NULL;
+ bool success;
+ int ret;
+ char buffer[4096];
+ size_t length = 0, nbytes;
+
+ success = input_stream_open(&input_stream, url, &error);
+ if (!success) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ while (!input_stream.ready) {
+ ret = input_stream_buffer(&input_stream, &error);
+ if (ret < 0) {
+ input_stream_close(&input_stream);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+ }
+
+ do {
+ nbytes = input_stream_read(&input_stream, buffer + length,
+ sizeof(buffer) - length, &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ if (input_stream_eof(&input_stream))
+ break;
+
+ /* I/O error */
+ input_stream_close(&input_stream);
+ return NULL;
+ }
+
+ length += nbytes;
+ } while (length < sizeof(buffer));
+
+ input_stream_close(&input_stream);
+ return g_strndup(buffer, length);
+}
+
+/**
+ * Ini-style value fetcher.
+ * @param response data through which to search.
+ * @param name name of value to search for.
+ * @return value for param name in param reponse or NULL on error. Free with g_free.
+ */
+static char *
+lastfm_find(const char *response, const char *name)
+{
+ size_t name_length = strlen(name);
+
+ while (true) {
+ const char *eol = strchr(response, '\n');
+ if (eol == NULL)
+ return NULL;
+
+ if (strncmp(response, name, name_length) == 0 &&
+ response[name_length] == '=') {
+ response += name_length + 1;
+ return g_strndup(response, eol - response);
+ }
+
+ response = eol + 1;
+ }
+}
+
+static struct playlist_provider *
+lastfm_open_uri(const char *uri)
+{
+ struct lastfm_playlist *playlist;
+ GError *error = NULL;
+ char *p, *q, *response, *session;
+ bool success;
+
+ /* handshake */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
+ "version=1.1.1&platform=linux&"
+ "username=", lastfm_config.user, "&"
+ "passwordmd5=", lastfm_config.md5, "&"
+ "debug=0&partner=", NULL);
+ response = lastfm_get(p);
+ g_free(p);
+ if (response == NULL)
+ return NULL;
+
+ /* extract session id from response */
+
+ session = lastfm_find(response, "session");
+ g_free(response);
+ if (session == NULL) {
+ g_warning("last.fm handshake failed");
+ return NULL;
+ }
+
+ q = g_uri_escape_string(session, NULL, false);
+ g_free(session);
+ session = q;
+
+ g_debug("session='%s'", session);
+
+ /* "adjust" last.fm radio */
+
+ if (strlen(uri) > 9) {
+ char *escaped_uri;
+
+ escaped_uri = g_uri_escape_string(uri, NULL, false);
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
+ "session=", session, "&url=", escaped_uri, "&debug=0",
+ NULL);
+ g_free(escaped_uri);
+
+ response = lastfm_get(p);
+ g_free(response);
+ g_free(p);
+
+ if (response == NULL) {
+ g_free(session);
+ return NULL;
+ }
+ }
+
+ /* create the playlist object */
+
+ playlist = g_new(struct lastfm_playlist, 1);
+ playlist_provider_init(&playlist->base, &lastfm_playlist_plugin);
+
+ /* open the last.fm playlist */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
+ "sk=", session, "&discovery=0&desktop=1.5.1.31879",
+ NULL);
+ g_free(session);
+
+ success = input_stream_open(&playlist->is, p, &error);
+ g_free(p);
+
+ if (!success) {
+ if (error != NULL) {
+ g_warning("Failed to load XSPF playlist: %s",
+ error->message);
+ g_error_free(error);
+ } else
+ g_warning("Failed to load XSPF playlist");
+ g_free(playlist);
+ return NULL;
+ }
+
+ while (!playlist->is.ready) {
+ int ret = input_stream_buffer(&playlist->is, &error);
+ if (ret < 0) {
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ if (ret == 0)
+ /* nothing was buffered - wait */
+ g_usleep(10000);
+ }
+
+ /* last.fm does not send a MIME type, we have to fake it here
+ :-( */
+ g_free(playlist->is.mime);
+ playlist->is.mime = g_strdup("application/xspf+xml");
+
+ /* parse the XSPF playlist */
+
+ playlist->xspf = playlist_list_open_stream(&playlist->is, NULL);
+ if (playlist->xspf == NULL) {
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+ g_warning("Failed to parse XSPF playlist");
+ return NULL;
+ }
+
+ return &playlist->base;
+}
+
+static void
+lastfm_close(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ playlist_plugin_close(playlist->xspf);
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+}
+
+static struct song *
+lastfm_read(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ return playlist_plugin_read(playlist->xspf);
+}
+
+static const char *const lastfm_schemes[] = {
+ "lastfm",
+ NULL
+};
+
+const struct playlist_plugin lastfm_playlist_plugin = {
+ .name = "lastfm",
+
+ .init = lastfm_init,
+ .finish = lastfm_finish,
+ .open_uri = lastfm_open_uri,
+ .close = lastfm_close,
+ .read = lastfm_read,
+
+ .schemes = lastfm_schemes,
+};
diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h
new file mode 100644
index 000000000..871a4a043
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin lastfm_playlist_plugin;
+
+#endif
diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c
new file mode 100644
index 000000000..dbabea2e6
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.c
@@ -0,0 +1,92 @@
+/*
+ * 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 "playlist/m3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+
+#include <glib.h>
+
+struct m3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+m3u_open_stream(struct input_stream *is)
+{
+ struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1);
+
+ playlist_provider_init(&playlist->base, &m3u_playlist_plugin);
+ playlist->tis = text_input_stream_new(is);
+
+ return &playlist->base;
+}
+
+static void
+m3u_close(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+static struct song *
+m3u_read(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+ const char *line;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL)
+ return NULL;
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ return song_remote_new(line);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ .name = "m3u",
+
+ .open_stream = m3u_open_stream,
+ .close = m3u_close,
+ .read = m3u_read,
+
+ .suffixes = m3u_suffixes,
+ .mime_types = m3u_mime_types,
+};
diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h
new file mode 100644
index 000000000..3cb4b8874
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c
new file mode 100644
index 000000000..30c62b76e
--- /dev/null
+++ b/src/playlist/pls_playlist_plugin.c
@@ -0,0 +1,219 @@
+/*
+ * 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 "playlist/pls_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+#include <glib.h>
+
+struct pls_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist)
+{
+ gchar *key;
+ gchar *value;
+ int length;
+ GError *error = NULL;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ g_debug("Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = NULL;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ }
+ }
+
+ while (num_entries > 0) {
+ struct song *song;
+ key = g_strdup_printf("File%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ g_debug("Invalid PLS entry %s: '%s'",key, error->message);
+ g_error_free(error);
+ g_free(key);
+ return;
+ }
+ g_free(key);
+
+ song = song_remote_new(value);
+ g_free(value);
+
+ key = g_strdup_printf("Title%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && value){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ tag_add_item(song->tag,TAG_TITLE, value);
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+ g_free(value);
+
+ key = g_strdup_printf("Length%i", num_entries);
+ length = g_key_file_get_integer(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && length > 0){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ song->tag->time = length;
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+
+ playlist->songs = g_slist_prepend(playlist->songs, song);
+ num_entries--;
+ }
+
+}
+
+static struct playlist_provider *
+pls_open_stream(struct input_stream *is)
+{
+ GError *error = NULL;
+ size_t nbytes;
+ char buffer[1024];
+ bool success;
+ GKeyFile *keyfile;
+ struct pls_playlist *playlist;
+ GString *kf_data = g_string_new("");
+
+ do {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_string_free(kf_data, TRUE);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ kf_data = g_string_append_len(kf_data, buffer,nbytes);
+ /* Limit to 64k */
+ } while(kf_data->len < 65536);
+
+ if (kf_data->len == 0) {
+ g_warning("KeyFile parser failed: No Data");
+ g_string_free(kf_data, TRUE);
+ return NULL;
+ }
+
+ keyfile = g_key_file_new();
+ success = g_key_file_load_from_data(keyfile,
+ kf_data->str, kf_data->len,
+ G_KEY_FILE_NONE, &error);
+
+ g_string_free(kf_data, TRUE);
+
+ if (!success) {
+ g_warning("KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ playlist = g_new(struct pls_playlist, 1);
+ playlist_provider_init(&playlist->base, &pls_playlist_plugin);
+ playlist->songs = NULL;
+
+ pls_parser(keyfile, playlist);
+
+ g_key_file_free(keyfile);
+ return &playlist->base;
+}
+
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+pls_close(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+
+ g_free(playlist);
+
+}
+
+static struct song *
+pls_read(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ NULL
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ NULL
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ .name = "pls",
+
+ .open_stream = pls_open_stream,
+ .close = pls_close,
+ .read = pls_read,
+
+ .suffixes = pls_suffixes,
+ .mime_types = pls_mime_types,
+};
diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h
new file mode 100644
index 000000000..7bf1951b8
--- /dev/null
+++ b/src/playlist/pls_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c
new file mode 100644
index 000000000..a36463500
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.c
@@ -0,0 +1,342 @@
+/*
+ * 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 "playlist/xspf_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "xspf"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct xspf_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, PLAYLIST, TRACKLIST, TRACK,
+ LOCATION,
+ } state;
+
+ /**
+ * The current tag within the "track" element. This is only
+ * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static void
+xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ G_GNUC_UNUSED const gchar **attribute_names,
+ G_GNUC_UNUSED const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "trackList") == 0)
+ parser->state = TRACKLIST;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "track") == 0) {
+ parser->state = TRACK;
+ parser->song = NULL;
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "location") == 0)
+ parser->state = LOCATION;
+ else if (strcmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag = TAG_TRACK;
+
+ break;
+
+ case LOCATION:
+ break;
+ }
+}
+
+static void
+xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = ROOT;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "tracklist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "track") == 0) {
+ if (parser->song != NULL)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+
+ parser->state = TRACKLIST;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case LOCATION:
+ parser->state = TRACK;
+ break;
+ }
+}
+
+static void
+xspf_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ case PLAYLIST:
+ case TRACKLIST:
+ break;
+
+ case TRACK:
+ if (parser->song != NULL &&
+ parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+
+ case LOCATION:
+ if (parser->song == NULL) {
+ char *uri = g_strndup(text, text_len);
+ parser->song = song_remote_new(uri);
+ g_free(uri);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser xspf_parser = {
+ .start_element = xspf_start_element,
+ .end_element = xspf_end_element,
+ .text = xspf_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+xspf_parser_destroy(gpointer data)
+{
+ struct xspf_parser *parser = data;
+
+ if (parser->state >= TRACK && parser->song != NULL)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct xspf_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+xspf_open_stream(struct input_stream *is)
+{
+ struct xspf_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct xspf_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the XSPF XML file */
+
+ context = g_markup_parse_context_new(&xspf_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, xspf_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #xspf_playlist object from the parsed song list */
+
+ playlist = g_new(struct xspf_playlist, 1);
+ playlist_provider_init(&playlist->base, &xspf_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+xspf_close(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+xspf_read(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ NULL
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ NULL
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ .name = "xspf",
+
+ .open_stream = xspf_open_stream,
+ .close = xspf_close,
+ .read = xspf_read,
+
+ .suffixes = xspf_suffixes,
+ .mime_types = xspf_mime_types,
+};
diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h
new file mode 100644
index 000000000..c2c36fbed
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif
diff --git a/src/playlist_control.c b/src/playlist_control.c
index 4359611fd..2f75b504f 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist_internal.h"
#include "player_control.h"
@@ -30,15 +31,7 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-enum {
- /**
- * When the "prev" command is received, rewind the current
- * track if this number of seconds has already elapsed.
- */
- PLAYLIST_PREV_UNLESS_ELAPSED = 10,
-};
-
-void stopPlaylist(struct playlist *playlist)
+void playlist_stop(struct playlist *playlist)
{
if (!playlist->playing)
return;
@@ -46,7 +39,7 @@ void stopPlaylist(struct playlist *playlist)
assert(playlist->current >= 0);
g_debug("stop");
- playerWait();
+ pc_stop();
playlist->queued = -1;
playlist->playing = false;
@@ -68,11 +61,11 @@ void stopPlaylist(struct playlist *playlist)
}
}
-enum playlist_result playPlaylist(struct playlist *playlist, int song)
+enum playlist_result playlist_play(struct playlist *playlist, int song)
{
unsigned i = song;
- clearPlayerError();
+ pc_clear_error();
if (song == -1) {
/* play any song ("current" song, or the first song */
@@ -83,7 +76,7 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
if (playlist->playing) {
/* already playing: unpause playback, just in
case it was paused, and return */
- playerSetPause(0);
+ pc_set_pause(false);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -115,28 +108,28 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
playlist->stop_on_error = false;
playlist->error_count = 0;
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playPlaylistById(struct playlist *playlist, int id)
+playlist_play_id(struct playlist *playlist, int id)
{
int song;
if (id == -1) {
- return playPlaylist(playlist, id);
+ return playlist_play(playlist, id);
}
song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playPlaylist(playlist, song);
+ return playlist_play(playlist, song);
}
void
-nextSongInPlaylist(struct playlist *playlist)
+playlist_next(struct playlist *playlist)
{
int next_order;
int current;
@@ -157,7 +150,7 @@ nextSongInPlaylist(struct playlist *playlist)
/* cancel single */
playlist->queue.single = false;
/* no song after this one: stop playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* reset "current song" */
playlist->current = -1;
@@ -174,50 +167,42 @@ nextSongInPlaylist(struct playlist *playlist)
queue_shuffle_order(&playlist->queue);
/* note that playlist->current and playlist->queued are
- now invalid, but playPlaylistOrderNumber() will
+ now invalid, but playlist_play_order() will
discard them anyway */
}
- playPlaylistOrderNumber(playlist, next_order);
+ playlist_play_order(playlist, next_order);
}
/* Consume mode removes each played songs. */
if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
}
-void previousSongInPlaylist(struct playlist *playlist)
+void playlist_previous(struct playlist *playlist)
{
if (!playlist->playing)
return;
- if (g_timer_elapsed(playlist->prev_elapsed, NULL) >= 1.0 &&
- getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
- /* re-start playing the current song (just like the
- "prev" button on CD players) */
+ assert(queue_length(&playlist->queue) > 0);
- playPlaylistOrderNumber(playlist, playlist->current);
+ if (playlist->current > 0) {
+ /* play the preceding song */
+ playlist_play_order(playlist,
+ playlist->current - 1);
+ } else if (playlist->queue.repeat) {
+ /* play the last song in "repeat" mode */
+ playlist_play_order(playlist,
+ queue_length(&playlist->queue) - 1);
} else {
- if (playlist->current > 0) {
- /* play the preceding song */
- playPlaylistOrderNumber(playlist,
- playlist->current - 1);
- } else if (playlist->queue.repeat) {
- /* play the last song in "repeat" mode */
- playPlaylistOrderNumber(playlist,
- queue_length(&playlist->queue) - 1);
- } else {
- /* re-start playing the current song if it's
- the first one */
- playPlaylistOrderNumber(playlist, playlist->current);
- }
+ /* re-start playing the current song if it's
+ the first one */
+ playlist_play_order(playlist, playlist->current);
}
-
- g_timer_start(playlist->prev_elapsed);
}
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time)
{
const struct song *queued;
unsigned i;
@@ -233,7 +218,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
else
i = song;
- clearPlayerError();
+ pc_clear_error();
playlist->stop_on_error = true;
playlist->error_count = 0;
@@ -241,7 +226,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
/* seeking is not within the current song - first
start playing the new song */
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
queued = NULL;
}
@@ -259,11 +244,11 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
}
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time)
+playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return seekSongInPlaylist(playlist, song, seek_time);
+ return playlist_seek_song(playlist, song, seek_time);
}
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
index b83dc0933..956c33d8e 100644
--- a/src/playlist_edit.c
+++ b/src/playlist_edit.c
@@ -23,6 +23,7 @@
*
*/
+#include "config.h"
#include "playlist_internal.h"
#include "player_control.h"
#include "database.h"
@@ -35,16 +36,16 @@
#include <unistd.h>
#include <stdlib.h>
-static void incrPlaylistVersion(struct playlist *playlist)
+static void playlist_increment_version(struct playlist *playlist)
{
queue_increment_version(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
-void clearPlaylist(struct playlist *playlist)
+void playlist_clear(struct playlist *playlist)
{
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* make sure there are no references to allocated songs
anymore */
@@ -58,7 +59,7 @@ void clearPlaylist(struct playlist *playlist)
playlist->current = -1;
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
}
#ifndef WIN32
@@ -86,41 +87,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return addSongToPlaylist(playlist, song, added_id);
+ return playlist_append_song(playlist, song, added_id);
}
#endif
-static struct song *
-song_by_url(const char *url)
-{
- struct song *song;
-
- song = db_get_song(url);
- if (song != NULL)
- return song;
-
- if (uri_has_scheme(url))
- return song_remote_new(url);
-
- return NULL;
-}
-
-enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id)
-{
- struct song *song;
-
- g_debug("add to playlist: %s", url);
-
- song = song_by_url(url);
- if (song == NULL)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return addSongToPlaylist(playlist, song, added_id);
-}
-
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id)
{
const struct song *queued;
@@ -147,7 +119,7 @@ addSongToPlaylist(struct playlist *playlist,
queue_length(&playlist->queue));
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -157,8 +129,38 @@ addSongToPlaylist(struct playlist *playlist,
return PLAYLIST_RESULT_SUCCESS;
}
+static struct song *
+song_by_uri(const char *uri)
+{
+ struct song *song;
+
+ song = db_get_song(uri);
+ if (song != NULL)
+ return song;
+
+ if (uri_has_scheme(uri))
+ return song_remote_new(uri);
+
+ return NULL;
+}
+
+enum playlist_result
+playlist_append_uri(struct playlist *playlist, const char *uri,
+ unsigned *added_id)
+{
+ struct song *song;
+
+ g_debug("add to playlist: %s", uri);
+
+ song = song_by_uri(uri);
+ if (song == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return playlist_append_song(playlist, song, added_id);
+}
+
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2)
{
const struct song *queued;
@@ -188,7 +190,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
playlist->current = song1;
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -196,7 +198,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
}
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2)
{
int song1 = queue_id_to_position(&playlist->queue, id1);
int song2 = queue_id_to_position(&playlist->queue, id2);
@@ -204,28 +206,25 @@ swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return swapSongsInPlaylist(playlist, song1, song2);
+ return playlist_swap_songs(playlist, song1, song2);
}
-enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song)
+static void
+playlist_delete_internal(struct playlist *playlist, unsigned song,
+ const struct song **queued_p)
{
- const struct song *queued;
unsigned songOrder;
- if (song >= queue_length(&playlist->queue))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- queued = playlist_get_queued_song(playlist);
+ assert(song < queue_length(&playlist->queue));
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
- bool paused = getPlayerState() == PLAYER_STATE_PAUSE;
+ bool paused = pc_get_state() == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
- playerWait();
+ pc_stop();
playlist->playing = false;
/* see which song is going to be played instead */
@@ -237,13 +236,13 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
if (playlist->current >= 0 && !paused)
/* play the song after the deleted one */
- playPlaylistOrderNumber(playlist, playlist->current);
+ playlist_play_order(playlist, playlist->current);
else
/* no songs left to play, stop playback
completely */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
- queued = NULL;
+ *queued_p = NULL;
} else if (playlist->current == (int)songOrder)
/* there's a "current song" but we're not playing
currently - clear "current" */
@@ -256,41 +255,80 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
queue_delete(&playlist->queue, song);
- incrPlaylistVersion(playlist);
-
/* update the "current" and "queued" variables */
if (playlist->current > (int)songOrder) {
playlist->current--;
}
+}
+
+enum playlist_result
+playlist_delete(struct playlist *playlist, unsigned song)
+{
+ const struct song *queued;
+
+ if (song >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ queued = playlist_get_queued_song(playlist);
+
+ playlist_delete_internal(playlist, song, &queued);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned id)
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end)
+{
+ const struct song *queued;
+
+ if (start >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (end > queue_length(&playlist->queue))
+ end = queue_length(&playlist->queue);
+
+ if (start >= end)
+ return PLAYLIST_RESULT_SUCCESS;
+
+ queued = playlist_get_queued_song(playlist);
+
+ do {
+ playlist_delete_internal(playlist, --end, &queued);
+ } while (end != start);
+
+ playlist_increment_version(playlist);
+ playlist_update_queued_song(playlist, queued);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist_delete_id(struct playlist *playlist, unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return deleteFromPlaylist(playlist, song);
+ return playlist_delete(playlist, song);
}
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
+playlist_delete_song(struct playlist *playlist, const struct song *song)
{
for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
if (song == queue_get(&playlist->queue, i))
- deleteFromPlaylist(playlist, i);
+ playlist_delete(playlist, i);
pc_song_deleted(song);
}
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to)
+playlist_move_range(struct playlist *playlist,
+ unsigned start, unsigned end, int to)
{
const struct song *queued;
int currentSong;
@@ -342,7 +380,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -350,16 +388,17 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
+playlist_move_id(struct playlist *playlist, unsigned id1, int to)
{
int song = queue_id_to_position(&playlist->queue, id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return moveSongRangeInPlaylist(playlist, song, song+1, to);
+ return playlist_move_range(playlist, song, song+1, to);
}
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end)
{
const struct song *queued;
@@ -399,7 +438,7 @@ void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
queue_shuffle_range(&playlist->queue, start, end);
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
}
diff --git a/src/playlist_global.c b/src/playlist_global.c
index fa810bbc3..8cfbf2c5d 100644
--- a/src/playlist_global.c
+++ b/src/playlist_global.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist.h"
#include "playlist_state.h"
#include "event_pipe.h"
@@ -37,10 +38,11 @@ playlist_tag_event(void)
static void
playlist_event(void)
{
- syncPlayerAndPlaylist(&g_playlist);
+ playlist_sync(&g_playlist);
}
-void initPlaylist(void)
+void
+playlist_global_init(void)
{
playlist_init(&g_playlist);
@@ -48,17 +50,8 @@ void initPlaylist(void)
event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event);
}
-void finishPlaylist(void)
+void
+playlist_global_finish(void)
{
playlist_finish(&g_playlist);
}
-
-void savePlaylistState(FILE *fp)
-{
- playlist_state_save(fp, &g_playlist);
-}
-
-void readPlaylistState(FILE *fp)
-{
- playlist_state_restore(fp, &g_playlist);
-}
diff --git a/src/playlist_internal.h b/src/playlist_internal.h
index af880691b..8b2f780cc 100644
--- a/src/playlist_internal.h
+++ b/src/playlist_internal.h
@@ -47,6 +47,6 @@ playlist_update_queued_song(struct playlist *playlist,
const struct song *prev);
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum);
+playlist_play_order(struct playlist *playlist, int orderNum);
#endif
diff --git a/src/playlist_list.c b/src/playlist_list.c
new file mode 100644
index 000000000..e0013b619
--- /dev/null
+++ b/src/playlist_list.c
@@ -0,0 +1,332 @@
+/*
+ * 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 "playlist_list.h"
+#include "playlist_plugin.h"
+#include "playlist/extm3u_playlist_plugin.h"
+#include "playlist/m3u_playlist_plugin.h"
+#include "playlist/xspf_playlist_plugin.h"
+#include "playlist/lastfm_playlist_plugin.h"
+#include "playlist/pls_playlist_plugin.h"
+#include "playlist/asx_playlist_plugin.h"
+#include "playlist/cue_playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "utils.h"
+#include "conf.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+static const struct playlist_plugin *const playlist_plugins[] = {
+ &extm3u_playlist_plugin,
+ &m3u_playlist_plugin,
+ &xspf_playlist_plugin,
+ &pls_playlist_plugin,
+ &asx_playlist_plugin,
+#ifdef ENABLE_LASTFM
+ &lastfm_playlist_plugin,
+#endif
+#ifdef HAVE_CUE
+ &cue_playlist_plugin,
+#endif
+ NULL
+};
+
+/** which plugins have been initialized successfully? */
+static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)];
+
+/**
+ * Find the "playlist" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the playlist plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+playlist_plugin_config(const char *plugin_name)
+{
+ const struct config_param *param = NULL;
+
+ assert(plugin_name != NULL);
+
+ while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "name", NULL);
+ if (name == NULL)
+ g_error("playlist configuration without 'plugin' name in line %d",
+ param->line);
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+void
+playlist_list_global_init(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+ const struct config_param *param =
+ playlist_plugin_config(plugin->name);
+
+ if (!config_get_block_bool(param, "enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ playlist_plugins_enabled[i] =
+ playlist_plugin_init(playlist_plugins[i], param);
+ }
+}
+
+void
+playlist_list_global_finish(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i)
+ if (playlist_plugins_enabled[i])
+ playlist_plugin_finish(playlist_plugins[i]);
+}
+
+/* g_uri_parse_scheme() was introduced in GLib 2.16 */
+#if !GLIB_CHECK_VERSION(2,16,0)
+static char *
+g_uri_parse_scheme(const char *uri)
+{
+ const char *end = strstr(uri, "://");
+ if (end == NULL)
+ return NULL;
+ return g_strndup(uri, end - uri);
+}
+#endif
+
+static struct playlist_provider *
+playlist_list_open_uri_scheme(const char *uri, bool *tried)
+{
+ char *scheme;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ scheme = g_uri_parse_scheme(uri);
+ if (scheme == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ assert(!tried[i]);
+
+ if (playlist_plugins_enabled[i] && plugin->open_uri != NULL &&
+ plugin->schemes != NULL &&
+ string_array_contains(plugin->schemes, scheme)) {
+ playlist = playlist_plugin_open_uri(plugin, uri);
+ if (playlist != NULL)
+ break;
+
+ tried[i] = true;
+ }
+ }
+
+ g_free(scheme);
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_uri_suffix(const char *uri, const bool *tried)
+{
+ const char *suffix;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ suffix = uri_get_suffix(uri);
+ if (suffix == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && !tried[i] &&
+ plugin->open_uri != NULL && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ playlist = playlist_plugin_open_uri(plugin, uri);
+ if (playlist != NULL)
+ break;
+ }
+ }
+
+ return playlist;
+}
+
+struct playlist_provider *
+playlist_list_open_uri(const char *uri)
+{
+ struct playlist_provider *playlist;
+ /** this array tracks which plugins have already been tried by
+ playlist_list_open_uri_scheme() */
+ bool tried[G_N_ELEMENTS(playlist_plugins) - 1];
+
+ assert(uri != NULL);
+
+ memset(tried, false, sizeof(tried));
+
+ playlist = playlist_list_open_uri_scheme(uri, tried);
+ if (playlist == NULL)
+ playlist = playlist_list_open_uri_suffix(uri, tried);
+
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_mime(struct input_stream *is)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(is->mime != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] &&
+ plugin->open_stream != NULL &&
+ plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, is->mime)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET, NULL);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] &&
+ plugin->open_stream != NULL &&
+ plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET, NULL);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri)
+{
+ const char *suffix;
+ struct playlist_provider *playlist;
+
+ if (is->mime != NULL) {
+ playlist = playlist_list_open_stream_mime(is);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ suffix = uri != NULL ? uri_get_suffix(uri) : NULL;
+ if (suffix != NULL) {
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ return NULL;
+}
+
+static bool
+playlist_suffix_supported(const char *suffix)
+{
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix))
+ return true;
+ }
+
+ return false;
+}
+
+struct playlist_provider *
+playlist_list_open_path(struct input_stream *is, const char *path_fs)
+{
+ GError *error = NULL;
+ const char *suffix;
+ struct playlist_provider *playlist;
+
+ assert(path_fs != NULL);
+
+ suffix = uri_get_suffix(path_fs);
+ if (suffix == NULL || !playlist_suffix_supported(suffix))
+ return NULL;
+
+ if (!input_stream_open(is, path_fs, &error)) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ while (!is->ready) {
+ int ret = input_stream_buffer(is, &error);
+ if (ret < 0) {
+ input_stream_close(is);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+ }
+
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist == NULL)
+ input_stream_close(is);
+
+ return playlist;
+}
diff --git a/src/playlist_list.h b/src/playlist_list.h
new file mode 100644
index 000000000..ebfc5c509
--- /dev/null
+++ b/src/playlist_list.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_LIST_H
+#define MPD_PLAYLIST_LIST_H
+
+struct playlist_provider;
+struct input_stream;
+
+/**
+ * Initializes all playlist plugins.
+ */
+void
+playlist_list_global_init(void);
+
+/**
+ * Deinitializes all playlist plugins.
+ */
+void
+playlist_list_global_finish(void);
+
+/**
+ * Opens a playlist by its URI.
+ */
+struct playlist_provider *
+playlist_list_open_uri(const char *uri);
+
+/**
+ * Opens a playlist from an input stream.
+ *
+ * @param is an #input_stream object which is open and ready
+ * @param uri optional URI which was used to open the stream; may be
+ * used to select the appropriate playlist plugin
+ */
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri);
+
+/**
+ * Opens a playlist from a local file.
+ *
+ * @param is an uninitialized #input_stream object (must be closed
+ * with input_stream_close() if this function succeeds)
+ * @param path_fs the path of the playlist file
+ * @return a playlist, or NULL on error
+ */
+struct playlist_provider *
+playlist_list_open_path(struct input_stream *is, const char *path_fs);
+
+#endif
diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h
new file mode 100644
index 000000000..3515af109
--- /dev/null
+++ b/src/playlist_plugin.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#ifndef MPD_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_PLUGIN_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct config_param;
+struct input_stream;
+struct tag;
+
+/**
+ * An object which provides the contents of a playlist.
+ */
+struct playlist_provider {
+ const struct playlist_plugin *plugin;
+};
+
+static inline void
+playlist_provider_init(struct playlist_provider *playlist,
+ const struct playlist_plugin *plugin)
+{
+ playlist->plugin = plugin;
+}
+
+struct playlist_plugin {
+ const char *name;
+
+ /**
+ * Initialize the plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or NULL
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const struct config_param *param);
+
+ /**
+ * Deinitialize a plugin which was initialized successfully.
+ * Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * Opens the playlist on the specified URI. This URI has
+ * either matched one of the schemes or one of the suffixes.
+ */
+ struct playlist_provider *(*open_uri)(const char *uri);
+
+ /**
+ * Opens the playlist in the specified input stream. It has
+ * either matched one of the suffixes or one of the MIME
+ * types.
+ */
+ struct playlist_provider *(*open_stream)(struct input_stream *is);
+
+ void (*close)(struct playlist_provider *playlist);
+
+ struct song *(*read)(struct playlist_provider *playlist);
+
+ const char *const*schemes;
+ const char *const*suffixes;
+ const char *const*mime_types;
+};
+
+/**
+ * Initialize a plugin.
+ *
+ * @param param a configuration block for this plugin, or NULL if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+static inline bool
+playlist_plugin_init(const struct playlist_plugin *plugin,
+ const struct config_param *param)
+{
+ return plugin->init != NULL
+ ? plugin->init(param)
+ : true;
+}
+
+/**
+ * Deinitialize a plugin which was initialized successfully.
+ */
+static inline void
+playlist_plugin_finish(const struct playlist_plugin *plugin)
+{
+ if (plugin->finish != NULL)
+ plugin->finish();
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri)
+{
+ return plugin->open_uri(uri);
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_stream(const struct playlist_plugin *plugin,
+ struct input_stream *is)
+{
+ return plugin->open_stream(is);
+}
+
+static inline void
+playlist_plugin_close(struct playlist_provider *playlist)
+{
+ playlist->plugin->close(playlist);
+}
+
+static inline struct song *
+playlist_plugin_read(struct playlist_provider *playlist)
+{
+ return playlist->plugin->read(playlist);
+}
+
+#endif
diff --git a/src/playlist_print.c b/src/playlist_print.c
index fd61ab62c..f414ee0ac 100644
--- a/src/playlist_print.c
+++ b/src/playlist_print.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_print.h"
#include "queue_print.h"
#include "stored_playlist.h"
@@ -69,7 +70,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist,
bool
playlist_print_current(struct client *client, const struct playlist *playlist)
{
- int current_position = getPlaylistCurrentSong(playlist);
+ int current_position = playlist_get_current_song(playlist);
if (current_position < 0)
return false;
diff --git a/src/playlist_queue.c b/src/playlist_queue.c
new file mode 100644
index 000000000..af7444e10
--- /dev/null
+++ b/src/playlist_queue.c
@@ -0,0 +1,283 @@
+/*
+ * 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 "playlist_queue.h"
+#include "playlist_list.h"
+#include "playlist_plugin.h"
+#include "stored_playlist.h"
+#include "database.h"
+#include "mapper.h"
+#include "song.h"
+#include "tag.h"
+#include "uri.h"
+#include "ls.h"
+#include "input_stream.h"
+
+static void
+merge_song_metadata(struct song *dest, const struct song *base,
+ const struct song *add)
+{
+ dest->tag = base->tag != NULL
+ ? (add->tag != NULL
+ ? tag_merge(base->tag, add->tag)
+ : tag_dup(base->tag))
+ : (add->tag != NULL
+ ? tag_dup(add->tag)
+ : NULL);
+
+ dest->mtime = base->mtime;
+ dest->start_ms = add->start_ms;
+ dest->end_ms = add->end_ms;
+}
+
+static struct song *
+apply_song_metadata(struct song *dest, const struct song *src)
+{
+ struct song *tmp;
+
+ assert(dest != NULL);
+ assert(src != NULL);
+
+ if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0)
+ return dest;
+
+ if (song_in_database(dest)) {
+ char *path_fs = map_song_fs(dest);
+ if (path_fs == NULL)
+ return dest;
+
+ tmp = song_file_new(path_fs, NULL);
+ merge_song_metadata(tmp, dest, src);
+ } else {
+ tmp = song_file_new(dest->uri, NULL);
+ merge_song_metadata(tmp, dest, src);
+ song_free(dest);
+ }
+
+ return tmp;
+}
+
+/**
+ * Verifies the song, returns NULL if it is unsafe. Translate the
+ * song to a new song object within the database, if it is a local
+ * file. The old song object is freed.
+ */
+static struct song *
+check_translate_song(struct song *song, const char *base_uri)
+{
+ struct song *dest;
+ char *uri;
+
+ if (song_in_database(song))
+ /* already ok */
+ return song;
+
+ if (uri_has_scheme(song->uri)) {
+ if (uri_supported_scheme(song->uri))
+ /* valid remote song */
+ return song;
+ else {
+ /* unsupported remote song */
+ song_free(song);
+ return NULL;
+ }
+ }
+
+ if (g_path_is_absolute(song->uri)) {
+ /* local files must be relative to the music
+ directory */
+ song_free(song);
+ return NULL;
+ }
+
+ if (base_uri != NULL)
+ uri = g_build_filename(base_uri, song->uri, NULL);
+ else
+ uri = g_strdup(song->uri);
+
+ if (uri_has_scheme(base_uri)) {
+ dest = song_remote_new(uri);
+ g_free(uri);
+ } else {
+ dest = db_get_song(uri);
+ g_free(uri);
+ if (dest == NULL) {
+ /* not found in database */
+ song_free(song);
+ return dest;
+ }
+ }
+
+ dest = apply_song_metadata(dest, song);
+ song_free(song);
+
+ return dest;
+}
+
+enum playlist_result
+playlist_load_into_queue(const char *uri, struct playlist_provider *source,
+ struct playlist *dest)
+{
+ enum playlist_result result;
+ struct song *song;
+ char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL;
+
+ while ((song = playlist_plugin_read(source)) != NULL) {
+ song = check_translate_song(song, base_uri);
+ if (song == NULL)
+ continue;
+
+ result = playlist_append_song(dest, song, NULL);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ if (!song_in_database(song))
+ song_free(song);
+ g_free(base_uri);
+ return result;
+ }
+ }
+
+ g_free(base_uri);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+static enum playlist_result
+playlist_open_remote_into_queue(const char *uri, struct playlist *dest)
+{
+ GError *error = NULL;
+ struct playlist_provider *playlist;
+ bool stream = false;
+ struct input_stream is;
+ enum playlist_result result;
+
+ assert(uri_has_scheme(uri));
+
+ playlist = playlist_list_open_uri(uri);
+ if (playlist == NULL) {
+ stream = input_stream_open(&is, uri, &error);
+ if (!stream) {
+ if (error != NULL) {
+ g_warning("Failed to open %s: %s",
+ uri, error->message);
+ g_error_free(error);
+ }
+
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+ }
+
+ playlist = playlist_list_open_stream(&is, uri);
+ if (playlist == NULL) {
+ input_stream_close(&is);
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+ }
+ }
+
+ result = playlist_load_into_queue(uri, playlist, dest);
+ playlist_plugin_close(playlist);
+
+ if (stream)
+ input_stream_close(&is);
+
+ return result;
+}
+
+static enum playlist_result
+playlist_open_path_into_queue(const char *path_fs, const char *uri,
+ struct playlist *dest)
+{
+ struct playlist_provider *playlist;
+ struct input_stream is;
+ enum playlist_result result;
+
+ if ((playlist = playlist_list_open_uri(path_fs)) != NULL)
+ result = playlist_load_into_queue(uri, playlist, dest);
+ else if ((playlist = playlist_list_open_path(&is, path_fs)) != NULL) {
+ result = playlist_load_into_queue(uri, playlist, dest);
+ input_stream_close(&is);
+ } else
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ playlist_plugin_close(playlist);
+
+ return result;
+}
+
+/**
+ * Load a playlist from the configured playlist directory.
+ */
+static enum playlist_result
+playlist_open_local_into_queue(const char *uri, struct playlist *dest)
+{
+ const char *playlist_directory_fs;
+ char *path_fs;
+ enum playlist_result result;
+
+ assert(spl_valid_name(uri));
+
+ playlist_directory_fs = map_spl_path();
+ if (playlist_directory_fs == NULL)
+ return PLAYLIST_RESULT_DISABLED;
+
+ path_fs = g_build_filename(playlist_directory_fs, uri, NULL);
+ result = playlist_open_path_into_queue(path_fs, NULL, dest);
+ g_free(path_fs);
+
+ return result;
+}
+
+/**
+ * Load a playlist from the configured music directory.
+ */
+static enum playlist_result
+playlist_open_local_into_queue2(const char *uri, struct playlist *dest)
+{
+ char *path_fs;
+ enum playlist_result result;
+
+ assert(uri_safe_local(uri));
+
+ path_fs = map_uri_fs(uri);
+ if (path_fs == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ result = playlist_open_path_into_queue(path_fs, uri, dest);
+ g_free(path_fs);
+
+ return result;
+}
+
+enum playlist_result
+playlist_open_into_queue(const char *uri, struct playlist *dest)
+{
+ if (uri_has_scheme(uri))
+ return playlist_open_remote_into_queue(uri, dest);
+
+ if (spl_valid_name(uri)) {
+ enum playlist_result result =
+ playlist_open_local_into_queue(uri, dest);
+ if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
+ return result;
+ }
+
+ if (uri_safe_local(uri))
+ return playlist_open_local_into_queue2(uri, dest);
+
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+}
diff --git a/src/playlist_queue.h b/src/playlist_queue.h
new file mode 100644
index 000000000..b1fc9dde9
--- /dev/null
+++ b/src/playlist_queue.h
@@ -0,0 +1,51 @@
+/*
+ * 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
+ * \brief Glue between playlist plugin and the play queue
+ */
+
+#ifndef MPD_PLAYLIST_QUEUE_H
+#define MPD_PLAYLIST_QUEUE_H
+
+#include "playlist.h"
+
+struct playlist_provider;
+struct playlist;
+
+/**
+ * Loads the contents of a playlist and append it to the specified
+ * play queue.
+ *
+ * @param uri the URI of the playlist, used to resolve relative song
+ * URIs
+ */
+enum playlist_result
+playlist_load_into_queue(const char *uri, struct playlist_provider *source,
+ struct playlist *dest);
+
+/**
+ * Opens a playlist with a playlist plugin and append to the specified
+ * play queue.
+ */
+enum playlist_result
+playlist_open_into_queue(const char *uri, struct playlist *dest);
+
+#endif
+
diff --git a/src/playlist_save.c b/src/playlist_save.c
index 13dbc721d..235842ae8 100644
--- a/src/playlist_save.c
+++ b/src/playlist_save.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_save.h"
#include "stored_playlist.h"
#include "song.h"
@@ -54,7 +55,7 @@ playlist_print_uri(FILE *file, const char *uri)
char *s;
if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
- uri[0] != '/')
+ !g_path_is_absolute(uri))
s = map_uri_fs(uri);
else
s = utf8_to_fs_charset(uri);
@@ -118,7 +119,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
/* for windows compatibility, convert slashes */
char *temp2 = g_strdup(temp);
char *p = temp2;
@@ -127,7 +128,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
*p = '/';
p++;
}
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
g_warning("can't add file \"%s\"", temp2);
}
g_free(temp2);
diff --git a/src/playlist_state.c b/src/playlist_state.c
index af0f7982b..ea8b7e4f9 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist_state.h"
#include "playlist.h"
#include "player_control.h"
@@ -51,10 +52,14 @@
void
playlist_state_save(FILE *fp, const struct playlist *playlist)
{
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
if (playlist->playing) {
- switch (getPlayerState()) {
+ switch (player_status.state) {
case PLAYER_STATE_PAUSE:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
break;
@@ -65,10 +70,16 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
queue_order_to_position(&playlist->queue,
playlist->current));
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
- getPlayerElapsedTime());
- } else
+ (int)player_status.elapsed_time);
+ } else {
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
+ if (playlist->current >= 0)
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
+ queue_order_to_position(&playlist->queue,
+ playlist->current));
+ }
+
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM,
playlist->queue.random);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT,
@@ -78,7 +89,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME,
playlist->queue.consume);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
- (int)(getPlayerCrossFade()));
+ (int)(pc_get_cross_fade()));
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
queue_save(fp, &playlist->queue);
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
@@ -109,8 +120,8 @@ playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer)
queue_increment_version(&playlist->queue);
}
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist)
+bool
+playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist)
{
int current = -1;
int seek_time = 0;
@@ -118,50 +129,45 @@ playlist_state_restore(FILE *fp, struct playlist *playlist)
char buffer[PLAYLIST_BUFFER_SIZE];
bool random_mode = false;
+ if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
+ return false;
+
+ line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
+
+ if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
+ state = PLAYER_STATE_PLAY;
+ else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
+ state = PLAYER_STATE_PAUSE;
+
while (fgets(buffer, sizeof(buffer), fp)) {
g_strchomp(buffer);
- if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) {
- if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
- state = PLAYER_STATE_PLAY;
- } else
- if (strcmp
- (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PAUSE)
- == 0) {
- state = PLAYER_STATE_PAUSE;
- }
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
+ if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0) {
- setPlaylistRepeatStatus(playlist, true);
+ playlist_set_repeat(playlist, true);
} else
- setPlaylistRepeatStatus(playlist, false);
+ playlist_set_repeat(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
"1") == 0) {
- setPlaylistSingleStatus(playlist, true);
+ playlist_set_single(playlist, true);
} else
- setPlaylistSingleStatus(playlist, false);
+ playlist_set_single(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
"1") == 0) {
- setPlaylistConsumeStatus(playlist, true);
+ playlist_set_consume(playlist, true);
} else
- setPlaylistConsumeStatus(playlist, false);
+ playlist_set_consume(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) {
- setPlayerCrossFade(atoi
- (&
- (buffer
- [strlen
- (PLAYLIST_STATE_FILE_CROSSFADE)])));
+ pc_set_cross_fade(atoi(buffer + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM),
@@ -172,24 +178,56 @@ playlist_state_restore(FILE *fp, struct playlist *playlist)
(PLAYLIST_STATE_FILE_CURRENT)]));
} else if (g_str_has_prefix(buffer,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
- if (state == PLAYER_STATE_STOP)
- current = -1;
playlist_state_load(fp, playlist, buffer);
}
}
- setPlaylistRandomStatus(playlist, random_mode);
+ playlist_set_random(playlist, random_mode);
- if (state != PLAYER_STATE_STOP && !queue_is_empty(&playlist->queue)) {
+ if (!queue_is_empty(&playlist->queue)) {
if (!queue_valid_position(&playlist->queue, current))
current = 0;
- if (seek_time == 0)
- playPlaylist(playlist, current);
+ /* enable all devices for the first time; this must be
+ called here, after the audio output states were
+ restored, before playback begins */
+ if (state != PLAYER_STATE_STOP)
+ pc_update_audio();
+
+ if (state == PLAYER_STATE_STOP /* && config_option */)
+ playlist->current = current;
+ else if (seek_time == 0)
+ playlist_play(playlist, current);
else
- seekSongInPlaylist(playlist, current, seek_time);
+ playlist_seek_song(playlist, current, seek_time);
if (state == PLAYER_STATE_PAUSE)
- playerPause();
+ pc_pause();
}
+
+ return true;
+}
+
+unsigned
+playlist_state_get_hash(const struct playlist *playlist)
+{
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
+ return playlist->queue.version ^
+ (player_status.state != PLAYER_STATE_STOP
+ ? ((int)player_status.elapsed_time << 8)
+ : 0) ^
+ (playlist->current >= 0
+ ? (queue_order_to_position(&playlist->queue,
+ playlist->current) << 16)
+ : 0) ^
+ ((int)pc_get_cross_fade() << 20) ^
+ (player_status.state << 24) ^
+ (playlist->queue.random << 27) ^
+ (playlist->queue.repeat << 28) ^
+ (playlist->queue.single << 29) ^
+ (playlist->queue.consume << 30) ^
+ (playlist->queue.random << 31);
}
diff --git a/src/playlist_state.h b/src/playlist_state.h
index 989430264..d116aaeb1 100644
--- a/src/playlist_state.h
+++ b/src/playlist_state.h
@@ -25,6 +25,7 @@
#ifndef PLAYLIST_STATE_H
#define PLAYLIST_STATE_H
+#include <stdbool.h>
#include <stdio.h>
struct playlist;
@@ -32,7 +33,16 @@ struct playlist;
void
playlist_state_save(FILE *fp, const struct playlist *playlist);
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist);
+bool
+playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist);
+
+/**
+ * Generates a hash number for the current state of the playlist and
+ * the playback options. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+playlist_state_get_hash(const struct playlist *playlist);
#endif