Index: plparse/totem-pl-parser.h
===================================================================
--- plparse/totem-pl-parser.h	(revision 255)
+++ plparse/totem-pl-parser.h	(working copy)
@@ -248,6 +248,8 @@
  * which can be overridden by inheriting classes
  * @playlist_ended: the generic signal handler for the #TotemPlParser::playlist-ended signal,
  * which can be overridden by inheriting classes
+ * @parsing_finished: the generic signal handler for the #TotemPlParser::parsing-finished signal,
+ * which can be overridden by inheriting classes
  *
  * The class structure for the #TotemPlParser type.
  **/
@@ -263,6 +265,8 @@
 				  GHashTable *metadata);
 	void (*playlist_ended) (TotemPlParser *parser,
 				const char *uri);
+	void (*parsing_finished) (TotemPlParser *parser,
+				  TotemPlParserResult retval);
 };
 
 /**
@@ -349,10 +353,16 @@
 
 TotemPlParserResult totem_pl_parser_parse (TotemPlParser *parser,
 					   const char *url, gboolean fallback);
+TotemPlParserResult totem_pl_parser_parse_async (TotemPlParser *parser,
+						 const char *url, gboolean fallback);
 TotemPlParserResult totem_pl_parser_parse_with_base (TotemPlParser *parser,
 						     const char *url,
 						     const char *base,
 						     gboolean fallback);
+TotemPlParserResult totem_pl_parser_parse_with_base_async (TotemPlParser *parser,
+							   const char *url,
+							   const char *base,
+							   gboolean fallback);
 
 TotemPlParser *totem_pl_parser_new (void);
 
Index: plparse/totem-pl-parser-private.h
===================================================================
--- plparse/totem-pl-parser-private.h	(revision 255)
+++ plparse/totem-pl-parser-private.h	(working copy)
@@ -76,7 +76,12 @@
 {
 	GList *ignore_schemes;
 	GList *ignore_mimetypes;
+	GMutex *ignore_mutex;
 
+	GThread *thread;
+	GAsyncQueue *parse_queue;
+	gboolean thread_stopping;
+
 	guint recurse_level;
 	guint fallback : 1;
 	guint recurse : 1;
Index: plparse/plparser.symbols
===================================================================
--- plparse/plparser.symbols	(revision 255)
+++ plparse/plparser.symbols	(working copy)
@@ -14,9 +14,11 @@
 totemplparser_marshal_VOID__STRING_STRING_STRING
 totem_pl_parser_new
 totem_pl_parser_parse
+totem_pl_parser_parse_async
 totem_pl_parser_parse_date
 totem_pl_parser_parse_duration
 totem_pl_parser_parse_with_base
+totem_pl_parser_parse_with_base_async
 totem_pl_parser_relative
 totem_pl_parser_result_get_type
 totem_pl_parser_type_get_type
Index: plparse/totem-pl-parser.c
===================================================================
--- plparse/totem-pl-parser.c	(revision 255)
+++ plparse/totem-pl-parser.c	(working copy)
@@ -31,6 +31,9 @@
  *
  * <example>
  *  <title>Reading a Playlist</title>
+ *  <para>This example loads a playlist synchronously. To load a playlist asynchronously, however, you simply
+ *        need to use totem_pl_parser_parse_async(), and optionally connect to #TotemPlParser::parsing-finished
+ *        to receive the ultimate #TotemPlParserResult value for the operation.</para>
  *  <programlisting>
  * TotemPlParser *pl = totem_pl_parser_new ();
  * g_object_set (pl, "recurse", FALSE, "disable-unsafe", TRUE, NULL);
@@ -234,6 +237,7 @@
 	ENTRY_PARSED,
 	PLAYLIST_STARTED,
 	PLAYLIST_ENDED,
+	PARSING_FINISHED,
 	LAST_SIGNAL
 };
 
@@ -244,12 +248,15 @@
 static void totem_pl_parser_class_init (TotemPlParserClass *klass);
 static void totem_pl_parser_base_class_finalize	(TotemPlParserClass *klass);
 static void totem_pl_parser_init       (TotemPlParser *parser);
+static void totem_pl_parser_dispose    (GObject *object);
 static void totem_pl_parser_finalize   (GObject *object);
 
 static void totem_pl_parser_init       (TotemPlParser      *self);
 static void totem_pl_parser_class_init (TotemPlParserClass *klass);
 static gpointer totem_pl_parser_parent_class = NULL;
 
+#define TOTEM_PL_PARSER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_PL_PARSER, TotemPlParserPrivate))
+
 GType
 totem_pl_parser_get_type (void)
 {
@@ -279,9 +286,13 @@
 	GParamSpec *pspec;
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+	if (!g_thread_supported ())
+		g_thread_init (NULL);
+
 	totem_pl_parser_parent_class = g_type_class_peek_parent (klass);
 	g_type_class_add_private (klass, sizeof (TotemPlParserPrivate));
 
+	object_class->dispose = totem_pl_parser_dispose;
 	object_class->finalize = totem_pl_parser_finalize;
 	object_class->set_property = totem_pl_parser_set_property;
 	object_class->get_property = totem_pl_parser_get_property;
@@ -360,6 +371,7 @@
 			      NULL, NULL,
 			      totemplparser_marshal_VOID__STRING_BOXED,
 			      G_TYPE_NONE, 2, G_TYPE_STRING, TOTEM_TYPE_PL_PARSER_METADATA);
+
 	/**
 	 * TotemPlParser::playlist-started:
 	 * @parser: the object which received the signal
@@ -398,6 +410,24 @@
 			      g_cclosure_marshal_VOID__STRING,
 			      G_TYPE_NONE, 1, G_TYPE_STRING);
 
+	/**
+	 * TotemPlParser::parsing-finished:
+	 * @parser: the object which received the signal
+	 * @retval: the #TotemPlParserResult return value from the parse operation
+	 *
+	 * The ::parsing-finished signal is emitted after the parse operation initiated
+	 * by totem_pl_parser_parse() or totem_pl_parser_parse_with_base() has finished
+	 * (successfully or not).
+	 */
+	totem_pl_parser_table_signals[PARSING_FINISHED] =
+		g_signal_new ("parsing-finished",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (TotemPlParserClass, parsing_finished),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__ENUM,
+			      G_TYPE_NONE, 1, TOTEM_TYPE_PL_PARSER_RESULT);
+
 	/* param specs */
 	totem_pl_parser_pspec_pool = g_param_spec_pool_new (FALSE);
 	pspec = g_param_spec_string ("url", "url",
@@ -620,6 +650,26 @@
 	return TOTEM_PL_PARSER (g_object_new (TOTEM_TYPE_PL_PARSER, NULL));
 }
 
+typedef struct {
+	TotemPlParser *parser;
+	char *playlist_uri;
+} PlaylistEndedSignalData;
+
+static gboolean
+emit_playlist_ended_signal (PlaylistEndedSignalData *data)
+{
+	g_signal_emit (data->parser,
+		       totem_pl_parser_table_signals[PLAYLIST_ENDED],
+		       0, data->playlist_uri);
+
+	/* Free the data */
+	g_object_unref (data->parser);
+	g_free (data->playlist_uri);
+	g_free (data);
+
+	return FALSE;
+}
+
 /**
  * totem_pl_parser_playlist_end:
  * @parser: a #TotemPlParser
@@ -631,9 +681,13 @@
 void
 totem_pl_parser_playlist_end (TotemPlParser *parser, const char *playlist_uri)
 {
-	g_signal_emit (G_OBJECT (parser),
-		       totem_pl_parser_table_signals[PLAYLIST_ENDED],
-		       0, playlist_uri);
+	PlaylistEndedSignalData *data;
+
+	data = g_new (PlaylistEndedSignalData, 1);
+	data->parser = g_object_ref (parser);
+	data->playlist_uri = g_strdup (playlist_uri);
+
+	g_idle_add ((GSourceFunc) emit_playlist_ended_signal, data);
 }
 
 static char *
@@ -1057,25 +1111,70 @@
 totem_pl_parser_init (TotemPlParser *parser)
 {
 	parser->priv = G_TYPE_INSTANCE_GET_PRIVATE (parser, TOTEM_TYPE_PL_PARSER, TotemPlParserPrivate);
+	parser->priv->ignore_mutex = g_mutex_new ();
 }
 
 static void
+totem_pl_parser_dispose (GObject *object)
+{
+	TotemPlParserPrivate *priv = TOTEM_PL_PARSER_GET_PRIVATE (object);
+
+	/* Tell the thread we're stopping */
+	priv->thread_stopping = TRUE;
+
+	/* Wait for the thread to finish */
+	if (priv->thread != NULL)
+		g_thread_join (priv->thread);
+	priv->thread = NULL;
+
+	/* Free the job queue */
+	if (priv->parse_queue != NULL)
+		g_async_queue_unref (priv->parse_queue);
+	priv->parse_queue = NULL;
+
+	G_OBJECT_CLASS (totem_pl_parser_parent_class)->dispose (object);
+}
+
+static void
 totem_pl_parser_finalize (GObject *object)
 {
-	TotemPlParser *parser = TOTEM_PL_PARSER (object);
+	TotemPlParserPrivate *priv = TOTEM_PL_PARSER_GET_PRIVATE (object);
 
 	g_return_if_fail (object != NULL);
-	g_return_if_fail (parser->priv != NULL);
+	g_return_if_fail (priv != NULL);
 
-	g_list_foreach (parser->priv->ignore_schemes, (GFunc) g_free, NULL);
-	g_list_free (parser->priv->ignore_schemes);
+	g_list_foreach (priv->ignore_schemes, (GFunc) g_free, NULL);
+	g_list_free (priv->ignore_schemes);
 
-	g_list_foreach (parser->priv->ignore_mimetypes, (GFunc) g_free, NULL);
-	g_list_free (parser->priv->ignore_mimetypes);
+	g_list_foreach (priv->ignore_mimetypes, (GFunc) g_free, NULL);
+	g_list_free (priv->ignore_mimetypes);
 
+	g_mutex_free (priv->ignore_mutex);
+
 	G_OBJECT_CLASS (totem_pl_parser_parent_class)->finalize (object);
 }
 
+typedef struct {
+	TotemPlParser *parser;
+	guint signal_id;
+	char *url;
+	GHashTable *metadata;
+} EntryParsedSignalData;
+
+static gboolean
+emit_entry_parsed_signal (EntryParsedSignalData *data)
+{
+	g_signal_emit (data->parser, data->signal_id, 0, data->url, data->metadata);
+
+	/* Free the data */
+	g_object_unref (data->parser);
+	g_free (data->url);
+	g_hash_table_unref (data->metadata);
+	g_free (data);
+
+	return FALSE;
+}
+
 static void
 totem_pl_parser_add_url_valist (TotemPlParser *parser,
 				const gchar *first_property_name,
@@ -1188,15 +1287,21 @@
 	}
 
 	if (g_hash_table_size (metadata) > 0 || url != NULL) {
-		if (is_playlist == FALSE) {
-			g_signal_emit (G_OBJECT (parser),
-				       totem_pl_parser_table_signals[ENTRY_PARSED],
-				       0, url, metadata);
-		} else {
-			g_signal_emit (G_OBJECT (parser),
-				       totem_pl_parser_table_signals[PLAYLIST_STARTED],
-				       0, url, metadata);
-		}
+		EntryParsedSignalData *data;
+
+		/* Make sure to emit the signals asynchronously, as we could be in the main loop
+		 * *or* a worker thread at this point. */
+		data = g_new (EntryParsedSignalData, 1);
+		data->parser = g_object_ref (parser);
+		data->url = g_strdup (url);
+		data->metadata = g_hash_table_ref (metadata);
+
+		if (is_playlist == FALSE)
+			data->signal_id = totem_pl_parser_table_signals[ENTRY_PARSED];
+		else
+			data->signal_id = totem_pl_parser_table_signals[PLAYLIST_STARTED];
+
+		g_idle_add ((GSourceFunc) emit_entry_parsed_signal, data);
 	}
 
 	g_hash_table_unref (metadata);
@@ -1275,15 +1380,23 @@
 {
 	GList *l;
 
-	if (parser->priv->ignore_schemes == NULL)
+	g_mutex_lock (parser->priv->ignore_mutex);
+
+	if (parser->priv->ignore_schemes == NULL) {
+		g_mutex_unlock (parser->priv->ignore_mutex);
 		return FALSE;
+	}
 
 	for (l = parser->priv->ignore_schemes; l != NULL; l = l->next) {
 		const char *scheme = l->data;
-		if (g_file_has_uri_scheme (file, scheme) != FALSE)
+		if (g_file_has_uri_scheme (file, scheme) != FALSE) {
+			g_mutex_unlock (parser->priv->ignore_mutex);
 			return TRUE;
+		}
 	}
 
+	g_mutex_unlock (parser->priv->ignore_mutex);
+
 	return FALSE;
 }
 
@@ -1293,16 +1406,24 @@
 {
 	GList *l;
 
-	if (parser->priv->ignore_mimetypes == NULL)
+	g_mutex_lock (parser->priv->ignore_mutex);
+
+	if (parser->priv->ignore_mimetypes == NULL) {
+		g_mutex_unlock (parser->priv->ignore_mutex);
 		return FALSE;
+	}
 
 	for (l = parser->priv->ignore_mimetypes; l != NULL; l = l->next)
 	{
 		const char *item = l->data;
-		if (strcmp (mimetype, item) == 0)
+		if (strcmp (mimetype, item) == 0) {
+			g_mutex_unlock (parser->priv->ignore_mutex);
 			return TRUE;
+		}
 	}
 
+	g_mutex_unlock (parser->priv->ignore_mutex);
+
 	return FALSE;
 }
 
@@ -1596,6 +1717,70 @@
 	return ret;
 }
 
+typedef struct {
+	char *url;
+	char *base;
+	gboolean fallback;
+	TotemPlParserResult retval;
+	TotemPlParser *parser;
+} ParseQueueItem;
+
+static void
+parse_queue_item_free (ParseQueueItem *item)
+{
+	g_free (item->url);
+	g_free (item->base);
+	g_object_unref (item->parser);
+
+	g_free (item);
+}
+
+static gboolean
+totem_pl_parser_thread_job_finished (ParseQueueItem *queue_item)
+{
+	g_signal_emit (queue_item->parser, totem_pl_parser_table_signals[PARSING_FINISHED], 0, queue_item->retval);
+	parse_queue_item_free (queue_item);
+	return FALSE;
+}
+
+static gpointer
+totem_pl_parser_thread_main (TotemPlParser *parser)
+{
+	while (parser->priv->thread_stopping == FALSE) {
+		GFile *file, *base_file;
+		TotemPlParserResult retval;
+		ParseQueueItem *queue_item;
+
+		/* Get a job off the queue */
+		queue_item = g_async_queue_pop (parser->priv->parse_queue);
+
+		/* Start parsing it */
+		file = g_file_new_for_uri (queue_item->url);
+		base_file = NULL;
+
+		if (totem_pl_parser_scheme_is_ignored (parser, file) != FALSE) {
+			g_object_unref (file);
+			retval = TOTEM_PL_PARSER_RESULT_UNHANDLED;
+		} else {
+			parser->priv->recurse_level = 0;
+			parser->priv->fallback = queue_item->fallback != FALSE;
+			if (queue_item->base != NULL)
+				base_file = g_file_new_for_uri (queue_item->base);
+			retval = totem_pl_parser_parse_internal (parser, file, base_file);
+
+			g_object_unref (file);
+			if (base_file != NULL)
+				g_object_unref (base_file);
+		}
+
+		/* Emit the "parsing-finished" signal for this job from the main thread */
+		queue_item->retval = retval;
+		g_idle_add ((GSourceFunc) totem_pl_parser_thread_job_finished, queue_item);
+	}
+
+	return NULL;
+}
+
 /**
  * totem_pl_parser_parse_with_base:
  * @parser: a #TotemPlParser
@@ -1605,8 +1790,14 @@
  * end of the playlist on parse failure
  *
  * Parses a playlist given by the absolute URL @url, using
- * @base to resolve relative paths where appropriate.
+ * @base to resolve relative paths where appropriate. This method is 
+ * synchronous, and will block on (e.g.) network requests to slow
+ * servers. totem_pl_parser_parse_with_base_async() is recommended instead.
  *
+ * This function will return %TOTEM_PL_PARSER_RESULT_SUCCESS on success,
+ * and either %TOTEM_PL_PARSER_RESULT_UNHANDLED or
+ * %TOTEM_PL_PARSER_RESULT_ERROR if the parameters were invalid.
+ *
  * Return value: a #TotemPlParserResult
  **/
 TotemPlParserResult
@@ -1618,8 +1809,7 @@
 
 	g_return_val_if_fail (TOTEM_IS_PL_PARSER (parser), TOTEM_PL_PARSER_RESULT_UNHANDLED);
 	g_return_val_if_fail (url != NULL, TOTEM_PL_PARSER_RESULT_UNHANDLED);
-	g_return_val_if_fail (strstr (url, "://") != NULL,
-			TOTEM_PL_PARSER_RESULT_ERROR);
+	g_return_val_if_fail (strstr (url, "://") != NULL, TOTEM_PL_PARSER_RESULT_ERROR);
 
 	file = g_file_new_for_uri (url);
 	base_file = NULL;
@@ -1643,14 +1833,77 @@
 }
 
 /**
+ * totem_pl_parser_parse_with_base_async:
+ * @parser: a #TotemPlParser
+ * @url: the URL of the playlist to parse
+ * @base: the base path for relative filenames
+ * @fallback: %TRUE if the parser should add the playlist URL to the
+ * end of the playlist on parse failure
+ *
+ * Starts asynchronous parsing of a playlist given by the absolute URL @url,
+ * using @base to resolve relative paths where appropriate.
+ *
+ * This method is asynchronous, and #TotemPlParser::parsing-finished will be
+ * emitted once parsing is finished (successfully or not).
+ *
+ * This function will return %TOTEM_PL_PARSER_RESULT_SUCCESS if the job
+ * was successfully added to the parse queue, and either
+ * %TOTEM_PL_PARSER_RESULT_UNHANDLED or %TOTEM_PL_PARSER_RESULT_ERROR
+ * if the parameters were invalid.
+ *
+ * Return value: a #TotemPlParserResult
+ **/
+TotemPlParserResult
+totem_pl_parser_parse_with_base_async (TotemPlParser *parser, const char *url,
+				       const char *base, gboolean fallback)
+{
+	ParseQueueItem *queue_item;
+
+	g_return_val_if_fail (TOTEM_IS_PL_PARSER (parser), TOTEM_PL_PARSER_RESULT_UNHANDLED);
+	g_return_val_if_fail (url != NULL, TOTEM_PL_PARSER_RESULT_UNHANDLED);
+	g_return_val_if_fail (strstr (url, "://") != NULL, TOTEM_PL_PARSER_RESULT_ERROR);
+
+	if (parser->priv->thread == NULL) {
+		GError *error = NULL;
+
+		/* Create the worker thread */
+		parser->priv->parse_queue = g_async_queue_new_full ((GDestroyNotify) parse_queue_item_free);
+		parser->priv->thread = g_thread_create ((GThreadFunc) totem_pl_parser_thread_main, parser, TRUE, &error);
+
+		if (error != NULL) {
+			g_warning ("Failed to create TotemPlParser worker thread: %s", error->message);
+			g_async_queue_unref (parser->priv->parse_queue);
+			g_error_free (error);
+
+			return TOTEM_PL_PARSER_RESULT_ERROR;
+		}
+	}
+
+	/* Thread already exists, so add the parse job to its queue */
+	queue_item = g_new (ParseQueueItem, 1);
+	queue_item->url = g_strdup (url);
+	queue_item->base = g_strdup (base);
+	queue_item->fallback = fallback;
+	queue_item->parser = g_object_ref (parser);
+
+	g_async_queue_push (parser->priv->parse_queue, queue_item);
+
+	return TOTEM_PL_PARSER_RESULT_SUCCESS;
+}
+
+/**
  * totem_pl_parser_parse:
  * @parser: a #TotemPlParser
  * @url: the URL of the playlist to parse
  * @fallback: %TRUE if the parser should add the playlist URL to the
  * end of the playlist on parse failure
  *
- * Parses a playlist given by the absolute URL @url.
+ * Parses a playlist given by the absolute URL @url. This method is 
+ * synchronous, and will block on (e.g.) network requests to slow
+ * servers. totem_pl_parser_parse_async() is recommended instead.
  *
+ * Return values are as totem_pl_parser_parse_with_base().
+ *
  * Return value: a #TotemPlParserResult
  **/
 TotemPlParserResult
@@ -1661,6 +1914,29 @@
 }
 
 /**
+ * totem_pl_parser_parse_async:
+ * @parser: a #TotemPlParser
+ * @url: the URL of the playlist to parse
+ * @fallback: %TRUE if the parser should add the playlist URL to the
+ * end of the playlist on parse failure
+ *
+ * Starts asynchronous parsing of a playlist given by the absolute URL @url.
+ *
+ * This method is asynchronous, and #TotemPlParser::parsing-finished will be
+ * emitted once parsing is finished (successfully or not).
+ *
+ * Return values are as totem_pl_parser_parse_with_base_async().
+ *
+ * Return value: a #TotemPlParserResult
+ **/
+TotemPlParserResult
+totem_pl_parser_parse_async (TotemPlParser *parser, const char *url,
+			     gboolean fallback)
+{
+	return totem_pl_parser_parse_with_base_async (parser, url, NULL, fallback);
+}
+
+/**
  * totem_pl_parser_add_ignored_scheme:
  * @parser: a #TotemPlParser
  * @scheme: the scheme to ignore
@@ -1676,11 +1952,15 @@
 
 	g_return_if_fail (TOTEM_IS_PL_PARSER (parser));
 
+	g_mutex_lock (parser->priv->ignore_mutex);
+
 	s = g_strdup (scheme);
 	if (s[strlen (s) - 1] == ':')
 		s[strlen (s) - 1] = '\0';
 	parser->priv->ignore_schemes = g_list_prepend
 		(parser->priv->ignore_schemes, s);
+
+	g_mutex_unlock (parser->priv->ignore_mutex);
 }
 
 /**
