diff --git a/backends/gstreamer/rb-player-gst-helper.c b/backends/gstreamer/rb-player-gst-helper.c
index f36d441..8167e01 100644
--- a/backends/gstreamer/rb-player-gst-helper.c
+++ b/backends/gstreamer/rb-player-gst-helper.c
@@ -125,3 +125,137 @@ rb_player_gst_find_element_with_property (GstElement *element, const char *prope
 	gst_iterator_free (iter);
 	return result;
 }
+
+/**
+ * rb_player_gst_process_embedded_image:
+ * @taglist:	a #GstTagList containing an image
+ * @tag:	the tag name
+ *
+ * Converts embedded image data extracted from a tag list into
+ * a #GdkPixbuf.  The returned #GdkPixbuf is owned by the caller.
+ *
+ * Returns: a #GdkPixbuf, or NULL.
+ */
+GdkPixbuf *
+rb_player_gst_process_embedded_image (const GstTagList *taglist, const char *tag)
+{
+	GstBuffer *buf;
+	GdkPixbufLoader *loader;
+	GdkPixbuf *pixbuf;
+	GError *error = NULL;
+	const GValue *val;
+
+	val = gst_tag_list_get_value_index (taglist, tag, 0);
+	if (val == NULL) {
+		rb_debug ("no value for tag %s in the tag list" , tag);
+		return NULL;
+	}
+
+	buf = gst_value_get_buffer (val);
+	if (buf == NULL) {
+		rb_debug ("apparently couldn't get image buffer");
+		return NULL;
+	}
+
+	/* probably should check media type?  text/uri-list won't work too well in a pixbuf loader */
+
+	loader = gdk_pixbuf_loader_new ();
+	rb_debug ("sending %d bytes to pixbuf loader", buf->size);
+	if (gdk_pixbuf_loader_write (loader, buf->data, buf->size, &error) == FALSE) {
+		rb_debug ("pixbuf loader doesn't like the data: %s", error->message);
+		g_error_free (error);
+		g_object_unref (loader);
+		return NULL;
+	}
+
+	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+	if (pixbuf != NULL) {
+		g_object_ref (pixbuf);
+	}
+	
+	gdk_pixbuf_loader_close (loader, NULL);
+	g_object_unref (loader);
+
+	if (pixbuf == NULL) {
+		rb_debug ("pixbuf loader didn't give us a pixbuf");
+		return NULL;
+	}
+
+	rb_debug ("returning embedded image: %d x %d / %d",
+		  gdk_pixbuf_get_width (pixbuf),
+		  gdk_pixbuf_get_height (pixbuf),
+		  gdk_pixbuf_get_bits_per_sample (pixbuf));
+	return pixbuf;
+}
+
+/**
+ * rb_player_gst_process_tag_string:
+ * @taglist:	a #GstTagList containing a string tag
+ * @tag:	tag name
+ * @field:	returns the #RBMetaDataField corresponding to the tag
+ * @value:	returns the tag value
+ *
+ * Processes a tag string, determining the metadata field identifier
+ * corresponding to the tag name, and converting the tag data into the
+ * appropriate value type.
+ *
+ * Return value: %TRUE if the tag was successfully converted.
+ */
+gboolean
+rb_player_gst_process_tag_string (const GstTagList *taglist,
+				  const char *tag,
+				  RBMetaDataField *field,
+				  GValue *value)
+{
+	const GValue *tagval;
+
+	if (gst_tag_list_get_tag_size (taglist, tag) < 0) {
+		rb_debug ("no values in taglist for tag %s", tag);
+		return FALSE;
+	}
+
+	/* only handle a few fields here */
+	if (!strcmp (tag, GST_TAG_TITLE))
+		*field = RB_METADATA_FIELD_TITLE;
+	else if (!strcmp (tag, GST_TAG_GENRE))
+		*field = RB_METADATA_FIELD_GENRE;
+	else if (!strcmp (tag, GST_TAG_COMMENT))
+		*field = RB_METADATA_FIELD_COMMENT;
+	else if (!strcmp (tag, GST_TAG_BITRATE))
+		*field = RB_METADATA_FIELD_BITRATE;
+#ifdef GST_TAG_MUSICBRAINZ_TRACKID
+	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID))
+		*field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
+#endif
+	else {
+		rb_debug ("tag %s doesn't correspond to a metadata field we're interested in", tag);
+		return FALSE;
+	}
+
+	/* most of the fields we care about are strings */
+	switch (*field) {
+	case RB_METADATA_FIELD_BITRATE:
+		g_value_init (value, G_TYPE_ULONG);
+		break;
+
+	case RB_METADATA_FIELD_TITLE:
+	case RB_METADATA_FIELD_GENRE:
+	case RB_METADATA_FIELD_COMMENT:
+	case RB_METADATA_FIELD_MUSICBRAINZ_TRACKID:
+	default:
+		g_value_init (value, G_TYPE_STRING);
+		break;
+	}
+
+	tagval = gst_tag_list_get_value_index (taglist, tag, 0);
+	if (!g_value_transform (tagval, value)) {
+		rb_debug ("Could not transform tag value type %s into %s",
+			  g_type_name (G_VALUE_TYPE (tagval)),
+			  g_type_name (G_VALUE_TYPE (value)));
+		g_value_unset (value);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
diff --git a/backends/gstreamer/rb-player-gst-helper.h b/backends/gstreamer/rb-player-gst-helper.h
index 85cb20e..57def70 100644
--- a/backends/gstreamer/rb-player-gst-helper.h
+++ b/backends/gstreamer/rb-player-gst-helper.h
@@ -29,8 +29,23 @@
 #ifndef __RB_PLAYER_GST_HELPER_H
 #define __RB_PLAYER_GST_HELPER_H
 
-GstElement *rb_player_gst_try_audio_sink (const char *plugin_name, const char *name);
+#include <gst/gst.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 
-GstElement *rb_player_gst_find_element_with_property (GstElement *element, const char *property);
+#include "rb-metadata.h"
+
+GstElement *	rb_player_gst_try_audio_sink (const char *plugin_name,
+					      const char *name);
+
+GstElement *	rb_player_gst_find_element_with_property (GstElement *element,
+							  const char *property);
+
+GdkPixbuf *	rb_player_gst_process_embedded_image (const GstTagList *taglist,
+						      const char *tag);
+
+gboolean	rb_player_gst_process_tag_string (const GstTagList *taglist,
+						  const char *tag,
+						  RBMetaDataField *field,
+						  GValue *value);
 
 #endif /* __RB_PLAYER_GST_HELPER_H */
diff --git a/backends/gstreamer/rb-player-gst-xfade.c b/backends/gstreamer/rb-player-gst-xfade.c
index e34e692..405ad02 100644
--- a/backends/gstreamer/rb-player-gst-xfade.c
+++ b/backends/gstreamer/rb-player-gst-xfade.c
@@ -156,6 +156,7 @@
 #include <math.h>
 
 #include <glib/gi18n.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 #include <gst/gst.h>
 #include <gst/controller/gstcontroller.h>
 #include <gst/base/gstbasetransform.h>
@@ -329,6 +330,8 @@ typedef struct
 
 	GSList *missing_plugins;
 	gulong  emit_missing_plugins_id;
+
+	GList *tags;
 } RBXFadeStream;
 
 #define RB_TYPE_XFADE_STREAM 	(rb_xfade_stream_get_type ())
@@ -1468,61 +1471,27 @@ schedule_stream_reap (RBPlayerGstXFade *player)
 static void
 process_tag (const GstTagList *list, const gchar *tag, RBXFadeStream *stream)
 {
-	int count;
 	RBMetaDataField field;
-	const GValue *val;
-	GValue newval = {0,};
-
-	count = gst_tag_list_get_tag_size (list, tag);
-	if (count < 1)
-		return;
-
-	rb_debug ("got tag %s for stream %s", tag, stream->uri);
-	/* only handle the subset of fields we use for iradio */
-	if (!strcmp (tag, GST_TAG_TITLE))
-		field = RB_METADATA_FIELD_TITLE;
-	else if (!strcmp (tag, GST_TAG_GENRE))
-		field = RB_METADATA_FIELD_GENRE;
-	else if (!strcmp (tag, GST_TAG_COMMENT))
-		field = RB_METADATA_FIELD_COMMENT;
-	else if (!strcmp (tag, GST_TAG_BITRATE))
-		field = RB_METADATA_FIELD_BITRATE;
-#ifdef GST_TAG_MUSICBRAINZ_TRACKID
-	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID))
-		field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
-#endif
-	else
-		return;
-
-	/* of those, all except bitrate are strings */
-	switch (field) {
-	case RB_METADATA_FIELD_BITRATE:
-		g_value_init (&newval, G_TYPE_ULONG);
-		break;
-
-	case RB_METADATA_FIELD_TITLE:
-	case RB_METADATA_FIELD_GENRE:
-	case RB_METADATA_FIELD_COMMENT:
-	case RB_METADATA_FIELD_MUSICBRAINZ_TRACKID:
-	default:
-		g_value_init (&newval, G_TYPE_STRING);
-		break;
-	}
-	val = gst_tag_list_get_value_index (list, tag, 0);
-	if (!g_value_transform (val, &newval)) {
-		rb_debug ("Could not transform tag value type %s into %s",
-			  g_type_name (G_VALUE_TYPE (val)),
-			  g_type_name (G_VALUE_TYPE (&newval)));
-		return;
+	GValue value = {0,};
+
+	/* process embedded images */
+	if (!strcmp (tag, GST_TAG_IMAGE) || !strcmp (tag, GST_TAG_PREVIEW_IMAGE)) {
+		GdkPixbuf *pixbuf;
+		pixbuf = rb_player_gst_process_embedded_image (list, tag);
+		if (pixbuf != NULL) {
+			_rb_player_emit_image (RB_PLAYER (stream->player),
+					       stream->stream_data,
+					       pixbuf);
+			g_object_unref (pixbuf);
+		}
+	} else if (rb_player_gst_process_tag_string (list, tag, &field, &value)) {
+		rb_debug ("emitting info field %d", field);
+		_rb_player_emit_info (RB_PLAYER (stream->player),
+				      stream->stream_data,
+				      field,
+				      &value);
+		g_value_unset (&value);
 	}
-
-	rb_debug ("emitting info field %d for uri %s", field, stream->uri);
-	_rb_player_emit_info (RB_PLAYER (stream->player),
-			      stream->stream_data,
-			      field,
-			      &newval);
-
-	g_value_unset (&newval);
 }
 
 
@@ -1671,8 +1640,12 @@ rb_player_gst_xfade_bus_cb (GstBus *bus, GstMessage *message, RBPlayerGstXFade *
 			GstTagList *tags;
 			gst_message_parse_tag (message, &tags);
 
-			gst_tag_list_foreach (tags, (GstTagForeachFunc) process_tag, stream);
-			gst_tag_list_free (tags);
+			if (stream->emitted_playing) {
+				gst_tag_list_foreach (tags, (GstTagForeachFunc) process_tag, stream);
+				gst_tag_list_free (tags);
+			} else {
+				stream->tags = g_list_append (stream->tags, tags);
+			}
 		}
 		break;
 
@@ -1701,8 +1674,23 @@ rb_player_gst_xfade_bus_cb (GstBus *bus, GstMessage *message, RBPlayerGstXFade *
 		if (stream == NULL) {
 			rb_debug ("got application message %s for unknown stream", name);
 		} else if (strcmp (name, STREAM_PLAYING_MESSAGE) == 0) {
+			GList *t;
+
 			rb_debug ("got stream playing message for %s", stream->uri);
 			_rb_player_emit_playing_stream (RB_PLAYER (player), stream->stream_data);
+
+			/* process any buffered tag lists we received while prerolling the stream */
+			for (t = stream->tags; t != NULL; t = t->next) {
+				GstTagList *tags;
+
+				tags = (GstTagList *)t->data;
+				rb_debug ("processing buffered taglist");
+				gst_tag_list_foreach (tags, (GstTagForeachFunc) process_tag, stream);
+				gst_tag_list_free (tags);
+			}
+			g_list_free (stream->tags);
+			stream->tags = NULL;
+
 		} else if (strcmp (name, FADE_IN_DONE_MESSAGE) == 0) {
 			/* do something? */
 		} else if (strcmp (name, FADE_OUT_DONE_MESSAGE) == 0) {
diff --git a/backends/gstreamer/rb-player-gst.c b/backends/gstreamer/rb-player-gst.c
index b19ce1e..9e2e13f 100644
--- a/backends/gstreamer/rb-player-gst.c
+++ b/backends/gstreamer/rb-player-gst.c
@@ -122,11 +122,6 @@ struct _RBPlayerGstPrivate
 	gboolean playing;
 	gboolean buffering;
 
-	guint idle_error_id;
-	guint idle_eos_id;
-	guint idle_buffering_id;
-	GHashTable *idle_info_ids;
-
 	GList *waiting_tees;
 	GstElement *sinkbin;
 	GstElement *tee;
@@ -139,26 +134,6 @@ struct _RBPlayerGstPrivate
 	guint tick_timeout_id;
 };
 
-typedef enum
-{
-	EOS,
-	INFO,
-	ERROR,
-	TICK,
-	BUFFERING,
-	EVENT,
-} RBPlayerGstSignalType;
-
-typedef struct
-{
-	int type;
-	RBPlayerGst *object;
-	RBMetaDataField info_field;
-	GError *error;
-	GValue *info;
-	guint id;
-} RBPlayerGstSignal;
-
 static gboolean rb_player_gst_sync_pipeline (RBPlayerGst *mp);
 static void rb_player_gst_gst_free_playbin (RBPlayerGst *player);
 
@@ -225,8 +200,6 @@ static void
 rb_player_gst_init (RBPlayerGst *mp)
 {
 	mp->priv = RB_PLAYER_GST_GET_PRIVATE (mp);
-	mp->priv->idle_info_ids = g_hash_table_new (NULL, NULL);
-
 }
 
 static void
@@ -256,7 +229,6 @@ rb_player_gst_finalize (GObject *object)
 
 	if (mp->priv->tick_timeout_id != 0)
 		g_source_remove (mp->priv->tick_timeout_id);
-	g_hash_table_destroy (mp->priv->idle_info_ids);
 
 	if (mp->priv->playbin) {
 		gst_element_set_state (mp->priv->playbin,
@@ -288,138 +260,29 @@ rb_player_gst_gst_free_playbin (RBPlayerGst *player)
 }
 
 static void
-destroy_idle_signal (gpointer signal_pointer)
-{
-	RBPlayerGstSignal *signal = signal_pointer;
-
-	if (signal->error)
-		g_error_free (signal->error);
-
-	if (signal->info) {
-		g_value_unset (signal->info);
-		g_free (signal->info);
-	}
-
-	if (signal->id != 0) {
-		g_hash_table_remove (signal->object->priv->idle_info_ids,
-				     GUINT_TO_POINTER (signal->id));
-	}
-
-	g_object_unref (G_OBJECT (signal->object));
-	g_free (signal);
-
-}
-
-static gboolean
-emit_signal_idle (RBPlayerGstSignal *signal)
-{
-	switch (signal->type) {
-	case ERROR:
-		_rb_player_emit_error (RB_PLAYER (signal->object),
-				       signal->object->priv->stream_data,
-				       signal->error);
-
-		/* close if not already closing */
-		if (signal->object->priv->uri != NULL)
-			rb_player_close (RB_PLAYER (signal->object), NULL, NULL);
-
-		break;
-
-	case EOS:
-		_rb_player_emit_eos (RB_PLAYER (signal->object),
-				     signal->object->priv->stream_data);
-		signal->object->priv->idle_eos_id = 0;
-		break;
-
-	case INFO:
-		_rb_player_emit_info (RB_PLAYER (signal->object),
-				      signal->object->priv->stream_data,
-				      signal->info_field,
-				      signal->info);
-		break;
-
-	case BUFFERING:
-		_rb_player_emit_buffering (RB_PLAYER (signal->object),
-					   signal->object->priv->stream_data,
-					   g_value_get_uint (signal->info));
-		signal->object->priv->idle_buffering_id = 0;
-		break;
-	case EVENT:
-		_rb_player_emit_event (RB_PLAYER (signal->object),
-				       signal->object->priv->stream_data,
-				       g_value_get_string (signal->info),
-				       NULL);
-		break;
-	}
-
-	return FALSE;
-}
-
-static void
 process_tag (const GstTagList *list, const gchar *tag, RBPlayerGst *player)
 {
-	int count;
 	RBMetaDataField field;
-	RBPlayerGstSignal *signal;
-	const GValue *val;
-	GValue *newval;
-
-	count = gst_tag_list_get_tag_size (list, tag);
-	if (count < 1)
-		return;
-
-	/* only handle the subset of fields we use for iradio */
-	if (!strcmp (tag, GST_TAG_TITLE))
-		field = RB_METADATA_FIELD_TITLE;
-	else if (!strcmp (tag, GST_TAG_GENRE))
-		field = RB_METADATA_FIELD_GENRE;
-	else if (!strcmp (tag, GST_TAG_COMMENT))
-		field = RB_METADATA_FIELD_COMMENT;
-	else if (!strcmp (tag, GST_TAG_BITRATE))
-		field = RB_METADATA_FIELD_BITRATE;
-#ifdef GST_TAG_MUSICBRAINZ_TRACKID
-	else if (!strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID))
-		field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
-#endif
-	else
-		return;
-
-	/* of those, all except bitrate are strings */
-	newval = g_new0 (GValue, 1);
-	switch (field) {
-	case RB_METADATA_FIELD_BITRATE:
-		g_value_init (newval, G_TYPE_ULONG);
-		break;
-
-	case RB_METADATA_FIELD_TITLE:
-	case RB_METADATA_FIELD_GENRE:
-	case RB_METADATA_FIELD_COMMENT:
-	case RB_METADATA_FIELD_MUSICBRAINZ_TRACKID:
-	default:
-		g_value_init (newval, G_TYPE_STRING);
-		break;
-	}
-	val = gst_tag_list_get_value_index (list, tag, 0);
-	if (!g_value_transform (val, newval)) {
-		rb_debug ("Could not transform tag value type %s into %s",
-			  g_type_name (G_VALUE_TYPE (val)),
-			  g_type_name (G_VALUE_TYPE (newval)));
-		g_free (newval);
-		return;
+	GValue value = {0,};
+
+	/* process embedded images */
+	if (!strcmp (tag, GST_TAG_IMAGE) || !strcmp (tag, GST_TAG_PREVIEW_IMAGE)) {
+		GdkPixbuf *pixbuf;
+		pixbuf = rb_player_gst_process_embedded_image (list, tag);
+		if (pixbuf != NULL) {
+			_rb_player_emit_image (RB_PLAYER (player),
+					       player->priv->stream_data,
+					       pixbuf);
+			g_object_unref (pixbuf);
+		}
+	} else if (rb_player_gst_process_tag_string (list, tag, &field, &value)) {
+		rb_debug ("emitting info field %d", field);
+		_rb_player_emit_info (RB_PLAYER (player),
+				      player->priv->stream_data,
+				      field,
+				      &value);
+		g_value_unset (&value);
 	}
-
-	signal = g_new0 (RBPlayerGstSignal, 1);
-	signal->object = player;
-	signal->info_field = field;
-	signal->info = newval;
-	signal->type = INFO;
-	signal->id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
-				      (GSourceFunc) emit_signal_idle,
-				      signal,
-				      destroy_idle_signal);
-
-	g_object_ref (G_OBJECT (player));
-	g_hash_table_insert (player->priv->idle_info_ids, GUINT_TO_POINTER (signal->id), NULL);
 }
 
 static void
@@ -514,7 +377,6 @@ rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp)
 		break;
 	}
 	case GST_MESSAGE_BUFFERING: {
-		RBPlayerGstSignal *signal;
 		const GstStructure *s;
 		gint progress;
 
@@ -543,31 +405,20 @@ rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp)
 			}
 			mp->priv->buffering = TRUE;
 		}
-		signal = g_new0 (RBPlayerGstSignal, 1);
-		signal->type = BUFFERING;
-
-		g_object_ref (G_OBJECT (mp));
-		signal->object = mp;
-		signal->info = g_new0 (GValue, 1);
-		g_value_init (signal->info, G_TYPE_UINT);
-		g_value_set_uint (signal->info, (guint)progress);
-		g_idle_add ((GSourceFunc) emit_signal_idle, signal);
+		
+		_rb_player_emit_buffering (RB_PLAYER (mp),
+					   mp->priv->stream_data,
+					   progress);
 		break;
 	}
 	case GST_MESSAGE_APPLICATION: {
-		RBPlayerGstSignal *signal;
 		const GstStructure *structure;
 
 		structure = gst_message_get_structure (message);
-		signal = g_new0 (RBPlayerGstSignal, 1);
-		signal->type = EVENT;
-
-		g_object_ref (G_OBJECT (mp));
-		signal->object = mp;
-		signal->info = g_new0 (GValue, 1);
-		g_value_init (signal->info, G_TYPE_STRING);
-		g_value_set_string (signal->info, gst_structure_get_name (structure));
-		g_idle_add ((GSourceFunc) emit_signal_idle, signal);
+		_rb_player_emit_event (RB_PLAYER (mp),
+				       mp->priv->stream_data,
+				       gst_structure_get_name (structure),
+				       NULL);
 	}
 	case GST_MESSAGE_ELEMENT: {
 		if (gst_is_missing_plugin_message (message)) {
@@ -890,14 +741,6 @@ rb_player_gst_open (RBPlayer *player,
 	return TRUE;
 }
 
-static void
-remove_idle_source (gpointer key, gpointer value, gpointer user_data)
-{
-	guint id = GPOINTER_TO_UINT (key);
-
-	g_source_remove (id);
-}
-
 static gboolean
 rb_player_gst_close (RBPlayer *player, const char *uri, GError **error)
 {
@@ -916,20 +759,6 @@ rb_player_gst_close (RBPlayer *player, const char *uri, GError **error)
 	g_free (mp->priv->uri);
 	mp->priv->uri = NULL;
 
-	if (mp->priv->idle_eos_id != 0) {
-		g_source_remove (mp->priv->idle_eos_id);
-		mp->priv->idle_eos_id = 0;
-	}
-	if (mp->priv->idle_error_id != 0) {
-		g_source_remove (mp->priv->idle_error_id);
-		mp->priv->idle_error_id = 0;
-	}
-	if (mp->priv->idle_buffering_id != 0) {
-		g_source_remove (mp->priv->idle_buffering_id);
-		mp->priv->idle_buffering_id = 0;
-	}
-	g_hash_table_foreach (mp->priv->idle_info_ids, remove_idle_source, NULL);
-
 	if (mp->priv->tick_timeout_id != 0) {
 		g_source_remove (mp->priv->tick_timeout_id);
 		mp->priv->tick_timeout_id = 0;
diff --git a/backends/rb-player.c b/backends/rb-player.c
index c7008ee..26f7879 100644
--- a/backends/rb-player.c
+++ b/backends/rb-player.c
@@ -42,6 +42,7 @@ enum {
 	TICK,
 	EVENT,
 	PLAYING_STREAM,
+	IMAGE,
 	LAST_SIGNAL
 };
 
@@ -230,6 +231,26 @@ rb_player_interface_init (RBPlayerIface *iface)
 			      G_TYPE_NONE,
 			      1,
 			      G_TYPE_POINTER);
+	
+	/**
+	 * RBPlayer::image:
+	 * @player: the #RBPlayer
+	 * @stream_data: data associated with the stream
+	 * @image: the image extracted from the stream
+	 *
+	 * The 'image' signal is emitted to provide access to images extracted
+	 * from the stream.
+	 */
+	signals[IMAGE] =
+		g_signal_new ("image",
+			      G_TYPE_FROM_INTERFACE (iface),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBPlayerIface, image),
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_OBJECT,
+			      G_TYPE_NONE,
+			      2,
+			      G_TYPE_POINTER, GDK_TYPE_PIXBUF);
 }
 
 GType
@@ -625,6 +646,21 @@ _rb_player_emit_playing_stream (RBPlayer *player, gpointer stream_data)
 	g_signal_emit (player, signals[PLAYING_STREAM], 0, stream_data);
 }
 
+/**
+ * _rb_player_emit_image:
+ * @player: a #RBPlayer implementation
+ * @stream_data: data associated with the stream
+ * @image: an image extracted from the stream
+ *
+ * Emits the 'image' signal to notify listeners of an image that
+ * has been extracted from the stream.  To be used by implementations only.
+ */
+void
+_rb_player_emit_image (RBPlayer *player, gpointer stream_data, GdkPixbuf *image)
+{
+	g_signal_emit (player, signals[IMAGE], 0, stream_data, image);
+}
+
 GQuark
 rb_player_error_quark (void)
 {
diff --git a/backends/rb-player.h b/backends/rb-player.h
index 67723f9..86ad5ce 100644
--- a/backends/rb-player.h
+++ b/backends/rb-player.h
@@ -32,6 +32,7 @@
 #define __RB_PLAYER_H
 
 #include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 #include "rb-metadata.h"
 
 G_BEGIN_DECLS
@@ -119,6 +120,9 @@ struct _RBPlayerIface
 	void		(*event)		(RBPlayer *player,
 						 gpointer stream_data,
 						 gpointer data);
+	void		(*image)		(RBPlayer *player,
+						 gpointer stream_data,
+						 GdkPixbuf *image);
 };
 
 GType		rb_player_get_type   (void);
@@ -160,6 +164,7 @@ void	_rb_player_emit_error (RBPlayer *player, gpointer stream_data, GError *erro
 void	_rb_player_emit_tick (RBPlayer *player, gpointer stream_data, long elapsed, long duration);
 void	_rb_player_emit_event (RBPlayer *player, gpointer stream_data, const char *name, gpointer data);
 void	_rb_player_emit_playing_stream (RBPlayer *player, gpointer stream_data);
+void	_rb_player_emit_image (RBPlayer *player, gpointer stream_data, GdkPixbuf *image);
 
 G_END_DECLS
 
diff --git a/lib/rb-marshal.list b/lib/rb-marshal.list
index 5a18afb..c2edac6 100644
--- a/lib/rb-marshal.list
+++ b/lib/rb-marshal.list
@@ -27,6 +27,7 @@ VOID:OBJECT,INT,POINTER
 VOID:POINTER,INT
 VOID:POINTER,INT,POINTER
 VOID:POINTER,LONG,LONG
+VOID:POINTER,OBJECT
 VOID:POINTER,POINTER
 VOID:POINTER,POINTER,POINTER
 VOID:POINTER,UINT
diff --git a/shell/rb-shell-player.c b/shell/rb-shell-player.c
index ad3f3ca..e6c1837 100644
--- a/shell/rb-shell-player.c
+++ b/shell/rb-shell-player.c
@@ -157,6 +157,7 @@ static void tick_cb (RBPlayer *player, RhythmDBEntry *entry, long elapsed, long
 static void error_cb (RBPlayer *player, RhythmDBEntry *entry, const GError *err, gpointer data);
 static void missing_plugins_cb (RBPlayer *player, RhythmDBEntry *entry, const char **details, const char **descriptions, RBShellPlayer *sp);
 static void playing_stream_cb (RBPlayer *player, RhythmDBEntry *entry, RBShellPlayer *shell_player);
+static void player_image_cb (RBPlayer *player, RhythmDBEntry *entry, GdkPixbuf *image, RBShellPlayer *shell_player);
 static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err);
 
 static void rb_shell_player_set_play_order (RBShellPlayer *player,
@@ -275,9 +276,7 @@ struct RBShellPlayerPrivate
 	float volume;
 
 	guint do_next_idle_id;
-	guint emit_playing_id;
 	guint unblock_play_id;
-	guint jump_to_current_id;
 	guint notify_playing_id;
 };
 
@@ -1000,6 +999,11 @@ rb_shell_player_init (RBShellPlayer *player)
 				 G_CALLBACK (missing_plugins_cb),
 				 player, 0);
 
+	g_signal_connect_object (player->priv->mmplayer,
+				 "image",
+				 G_CALLBACK (player_image_cb),
+				 player, 0);
+
 	{
 		GVolumeMonitor *monitor = g_volume_monitor_get ();
 		g_signal_connect (G_OBJECT (monitor),
@@ -1226,21 +1230,11 @@ rb_shell_player_dispose (GObject *object)
 		player->priv->do_next_idle_id = 0;
 	}
 
-	if (player->priv->emit_playing_id != 0) {
-		g_source_remove (player->priv->emit_playing_id);
-		player->priv->emit_playing_id = 0;
-	}
-
 	if (player->priv->unblock_play_id != 0) {
 		g_source_remove (player->priv->unblock_play_id);
 		player->priv->unblock_play_id = 0;
 	}
 
-	if (player->priv->jump_to_current_id != 0) {
-		g_source_remove (player->priv->jump_to_current_id);
-		player->priv->jump_to_current_id = 0;
-	}
-
 	if (player->priv->notify_playing_id != 0) {
 		g_source_remove (player->priv->notify_playing_id);
 		player->priv->notify_playing_id = 0;
@@ -1926,16 +1920,6 @@ rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
 	g_object_set (action, "sensitive", have_next, NULL);
 }
 
-static gboolean
-rb_shell_player_jump_to_current_cb (RBShellPlayer *player)
-{
-	GDK_THREADS_ENTER ();
-	player->priv->jump_to_current_id = 0;
-	rb_shell_player_jump_to_current (player);
-	GDK_THREADS_LEAVE ();
-	return FALSE;
-}
-
 static void
 rb_shell_player_sync_song_position_slider_visibility (RBShellPlayer *player)
 {
@@ -3196,12 +3180,6 @@ rb_shell_player_stop (RBShellPlayer *player)
 		g_error_free (error);
 	}
 
-	/* make sure we don't say something else is playing after this */
-	if (player->priv->emit_playing_id > 0) {
-		g_source_remove (player->priv->emit_playing_id);
-		player->priv->emit_playing_id = 0;
-	}
-
 	if (player->priv->playing_entry != NULL) {
 		rhythmdb_entry_unref (player->priv->playing_entry);
 		player->priv->playing_entry = NULL;
@@ -3458,74 +3436,18 @@ rb_shell_player_error (RBShellPlayer *player,
 	}
 }
 
-static gboolean
-new_playing_stream_idle_cb (RBShellPlayer *player)
-{
-	RhythmDBEntry *entry;
-	const char *location;
-
-	GDK_THREADS_ENTER ();
-
-	player->priv->emit_playing_id = 0;
-
-	/* emit some interesting signals */
-	entry = rb_shell_player_get_playing_entry (player);
-	if (entry == NULL) {
-		/* this should never happen */
-		GDK_THREADS_LEAVE ();
-		g_return_val_if_fail (entry != NULL, FALSE);
-	}
-
-	location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-	rb_debug ("new playing stream: %s", location);
-	g_signal_emit (G_OBJECT (player),
-		       rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
-		       entry);
-	g_signal_emit (G_OBJECT (player),
-		       rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
-		       location);
-
-	/* resync UI */
-	rb_shell_player_sync_with_source (player);
-	rb_shell_player_sync_buttons (player);
-	g_object_notify (G_OBJECT (player), "playing");
-
-	GDK_THREADS_LEAVE ();
-	return FALSE;
-}
-
-static gboolean
-current_playing_stream_idle_cb (RBShellPlayer *player)
-{
-	GDK_THREADS_ENTER ();
-
-	player->priv->emit_playing_id = 0;
-
-	/* resync UI */
-	rb_shell_player_sync_with_source (player);
-	rb_shell_player_sync_buttons (player);
-	g_object_notify (G_OBJECT (player), "playing");
-
-	GDK_THREADS_LEAVE ();
-	return FALSE;
-}
-
 static void
 playing_stream_cb (RBPlayer *mmplayer,
 		   RhythmDBEntry *entry,
 		   RBShellPlayer *player)
 {
-	GDK_THREADS_ENTER ();
+	gboolean entry_changed;
 
-	if (player->priv->emit_playing_id != 0)
-		g_source_remove (player->priv->emit_playing_id);
+	g_return_if_fail (entry != NULL);
 
-	/* only emit playing-song-changed etc. when the entry changes */
-	if (player->priv->playing_entry != entry) {
-		player->priv->emit_playing_id = g_idle_add ((GSourceFunc) new_playing_stream_idle_cb, player);
-	} else {
-		player->priv->emit_playing_id = g_idle_add ((GSourceFunc) current_playing_stream_idle_cb, player);
-	}
+	GDK_THREADS_ENTER ();
+
+	entry_changed = (player->priv->playing_entry != entry);
 
 	/* update playing entry */
 	if (player->priv->playing_entry)
@@ -3533,18 +3455,26 @@ playing_stream_cb (RBPlayer *mmplayer,
 	player->priv->playing_entry = rhythmdb_entry_ref (entry);
 	player->priv->playing_entry_eos = FALSE;
 
+	if (entry_changed) {
+		const char *location;
+
+		location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+		rb_debug ("new playing stream: %s", location);
+		g_signal_emit (G_OBJECT (player),
+			       rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
+			       entry);
+		g_signal_emit (G_OBJECT (player),
+			       rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
+			       location);
+	}
+	
+	/* resync UI */
+	rb_shell_player_sync_with_source (player);
+	rb_shell_player_sync_buttons (player);
+	g_object_notify (G_OBJECT (player), "playing");
+
 	if (player->priv->jump_to_playing_entry) {
-		/* do this in a timeout because if we do it immediately (or even in
-		 * another idle handler), it causes gaps in the audio output.
-		 * I have no idea why this is.  Even 250ms later, it's mostly OK and
-		 * the UI doesn't look too sluggish.  hmm.
-		 */
-		if (player->priv->jump_to_current_id == 0) {
-			player->priv->jump_to_current_id =
-				g_timeout_add (250,
-					       (GSourceFunc) rb_shell_player_jump_to_current_cb,
-					       player);
-		}
+		rb_shell_player_jump_to_current (player);
 		player->priv->jump_to_playing_entry = FALSE;
 	}
 
@@ -3733,6 +3663,29 @@ missing_plugins_cb (RBPlayer *player,
 	g_closure_sink (retry);
 }
 
+static void
+player_image_cb (RBPlayer *player,
+		 RhythmDBEntry *entry,
+		 GdkPixbuf *image,
+		 RBShellPlayer *shell_player)
+{
+	GValue v = {0,};
+
+	if (image == NULL) {
+		return;
+	}
+
+	g_value_init (&v, GDK_TYPE_PIXBUF);
+	g_value_set_object (&v, image);
+
+	rhythmdb_emit_entry_extra_metadata_notify (shell_player->priv->db,
+						   entry,
+						   "rb:coverArt",
+						   &v);
+	g_value_unset (&v);
+
+}
+
 /**
  * rb_shell_player_get_playing_path:
  * @player: the #RBShellPlayer
