[rhythmbox] player: rewrite non-crossfading backend using playbin2 (bug #542922)



commit 11b1567a6462bfb333a227d8bb571f3777e0144f
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sat May 23 21:33:34 2009 +1000

    player: rewrite non-crossfading backend using playbin2 (bug #542922)
    
    Amongst other things, playbin2 does chained oggs correctly (bug #396409)
    and doesn't require us to have a video sink running at all times in case
    the user enables visualization (bug #406807).
    
    The fake visualizer element and associated hackery can now be removed
    from the visualization plugin, since we can just enable and disable vis
    as required.
    
    playbin2 exposes a network buffer size property, so we don't need to
    disable the slider controlling that any more.
    
    This increases the GStreamer version requirement to 0.10.20.
---
 backends/gstreamer/rb-player-gst.c        | 1162 +++++++++++++++--------------
 configure.ac                              |    2 +-
 plugins/visualizer/Makefile.am            |    3 +-
 plugins/visualizer/rb-fake-visualizer.c   |  609 ---------------
 plugins/visualizer/rb-visualizer-plugin.c |   62 +-
 shell/rb-shell-preferences.c              |    1 -
 6 files changed, 640 insertions(+), 1199 deletions(-)

diff --git a/backends/gstreamer/rb-player-gst.c b/backends/gstreamer/rb-player-gst.c
index 7a35e41..bcae010 100644
--- a/backends/gstreamer/rb-player-gst.c
+++ b/backends/gstreamer/rb-player-gst.c
@@ -1,9 +1,8 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- *  arch-tag: Implementation of GStreamer backends, with workarounds for bugs
- *
  *  Copyright (C) 2003 Jorn Baayen <jorn nl linux org>
  *  Copyright (C) 2003,2004 Colin Walters <walters debian org>
+ *  Copyright (C) 2009 Jonathan Matthew  <jonathan d14n 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
@@ -44,59 +43,32 @@
 #include "rb-util.h"
 
 #include "rb-player.h"
-#include "rb-player-gst-filter.h"
-#include "rb-player-gst-tee.h"
-/*#include "rb-player-gst-data-tee.h"*/
 #include "rb-player-gst.h"
 #include "rb-player-gst-helper.h"
+#include "rb-player-gst-filter.h"
+#include "rb-player-gst-tee.h"
 
 static void rb_player_init (RBPlayerIface *iface);
 static void rb_player_gst_filter_init (RBPlayerGstFilterIface *iface);
 static void rb_player_gst_tee_init (RBPlayerGstTeeIface *iface);
-/*tatic void rb_player_gst_data_tee_init (RBPlayerGstDataTeeIface *iface);*/
-static void rb_player_gst_finalize (GObject *object);
-static void rb_player_gst_get_property (GObject *object,
-					guint prop_id,
-					GValue *value,
-					GParamSpec *pspec);
-
-static gboolean rb_player_gst_open (RBPlayer *player,
-				    const char *uri,
-				    gpointer stream_data,
-				    GDestroyNotify stream_data_destroy,
-				    GError **error);
-static gboolean rb_player_gst_opened (RBPlayer *player);
-static gboolean rb_player_gst_close (RBPlayer *player,
-				     const char *uri,
-				     GError **error);
-static gboolean rb_player_gst_play (RBPlayer *player, gint crossfade, GError **error);
-static void rb_player_gst_pause (RBPlayer *player);
-static gboolean rb_player_gst_playing (RBPlayer *player);
-static void rb_player_gst_set_volume (RBPlayer *player, float volume);
-static float rb_player_gst_get_volume (RBPlayer *player);
-static void rb_player_gst_set_replaygain (RBPlayer *player,
-					  const char *uri,
-					  double track_gain, double track_peak,
-					  double album_gain, double album_peak);
-static gboolean rb_player_gst_seekable (RBPlayer *player);
-static void rb_player_gst_set_time (RBPlayer *player, long time);
-static long rb_player_gst_get_time (RBPlayer *player);
 
 G_DEFINE_TYPE_WITH_CODE(RBPlayerGst, rb_player_gst, G_TYPE_OBJECT,
 			G_IMPLEMENT_INTERFACE(RB_TYPE_PLAYER, rb_player_init)
 			G_IMPLEMENT_INTERFACE(RB_TYPE_PLAYER_GST_FILTER, rb_player_gst_filter_init)
 			G_IMPLEMENT_INTERFACE(RB_TYPE_PLAYER_GST_TEE, rb_player_gst_tee_init)
-			/*G_IMPLEMENT_INTERFACE(RB_TYPE_PLAYER_GST_DATA_TEE, rb_player_gst_data_tee_init)*/
 			)
-#define RB_PLAYER_GST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PLAYER_GST, RBPlayerGstPrivate))
+
+#define MAX_NETWORK_BUFFER_SIZE		(2048)
 
 #define RB_PLAYER_GST_TICK_HZ 5
+#define STATE_CHANGE_MESSAGE_TIMEOUT 5
 
 enum
 {
 	PROP_0,
 	PROP_PLAYBIN,
-	PROP_BUS
+	PROP_BUS,
+	PROP_BUFFER_SIZE
 };
 
 enum
@@ -109,170 +81,85 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 struct _RBPlayerGstPrivate
 {
+	char *prev_uri;
 	char *uri;
 	gpointer stream_data;
 	GDestroyNotify stream_data_destroy;
 
 	GstElement *playbin;
-	GstElement *volume_handler;
-
-	gboolean can_signal_direct_error;
-	GError *error;
-	gboolean emitted_error;
+	GstElement *audio_sink;
+	guint buffer_size;
 
 	gboolean playing;
 	gboolean buffering;
 
-	GList *waiting_tees;
-	GstElement *sinkbin;
-	GstElement *tee;
+	gboolean stream_change_pending;
+	gboolean current_track_finishing;
 
-	GList *waiting_filters; /* in reverse order */
-	GstElement *filterbin;
+	gboolean emitted_error;
 
 	float cur_volume;
+	float replaygain_scale;
 
 	guint tick_timeout_id;
-};
-
-static gboolean rb_player_gst_sync_pipeline (RBPlayerGst *mp);
-static void rb_player_gst_gst_free_playbin (RBPlayerGst *player);
-
-static void
-rb_player_gst_class_init (RBPlayerGstClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->finalize = rb_player_gst_finalize;
-	object_class->get_property = rb_player_gst_get_property;
-
-	g_object_class_install_property (object_class,
-					 PROP_PLAYBIN,
-					 g_param_spec_object ("playbin",
-						 	      "playbin",
-							      "playbin element",
-							      GST_TYPE_ELEMENT,
-							      G_PARAM_READABLE));
-	g_object_class_install_property (object_class,
-					 PROP_BUS,
-					 g_param_spec_object ("bus",
-							      "bus",
-							      "GStreamer message bus",
-							      GST_TYPE_BUS,
-							      G_PARAM_READABLE));
-	
-	signals[MISSING_PLUGINS] =
-		g_signal_new ("missing-plugins",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      0,	/* no point handling this internally */
-			      NULL, NULL,
-			      rb_marshal_VOID__POINTER_POINTER_POINTER,
-			      G_TYPE_NONE,
-			      3,
-			      G_TYPE_POINTER, G_TYPE_STRV, G_TYPE_STRV);
 
-	g_type_class_add_private (klass, sizeof (RBPlayerGstPrivate));
-}
+	GList *waiting_tees;
+	GstElement *sinkbin;
+	GstElement *tee;
 
-static void
-rb_player_init (RBPlayerIface *iface)
-{
-	iface->open = rb_player_gst_open;
-	iface->opened = rb_player_gst_opened;
-	iface->close = rb_player_gst_close;
-	iface->play = rb_player_gst_play;
-	iface->pause = rb_player_gst_pause;
-	iface->playing = rb_player_gst_playing;
-	iface->set_volume = rb_player_gst_set_volume;
-	iface->get_volume = rb_player_gst_get_volume;
-	iface->set_replaygain = rb_player_gst_set_replaygain;
-	iface->seekable = rb_player_gst_seekable;
-	iface->set_time = rb_player_gst_set_time;
-	iface->get_time = rb_player_gst_get_time;
-	iface->multiple_open = (RBPlayerFeatureFunc) rb_false_function;
-}
+	GList *waiting_filters; /* in reverse order */
+	GstElement *filterbin;
+};
 
 static gboolean
 tick_timeout (RBPlayerGst *mp)
 {
-	if (mp->priv->playing == FALSE)
-		return TRUE;
-
-	_rb_player_emit_tick (RB_PLAYER (mp), mp->priv->stream_data, rb_player_get_time (RB_PLAYER (mp)), -1);
-
+	if (mp->priv->playing) {
+		_rb_player_emit_tick (RB_PLAYER (mp),
+				      mp->priv->stream_data,
+				      rb_player_get_time (RB_PLAYER (mp)),
+				      -1);
+	}
 	return TRUE;
 }
 
-static void
-rb_player_gst_init (RBPlayerGst *mp)
-{
-	mp->priv = RB_PLAYER_GST_GET_PRIVATE (mp);
-}
 
 static void
-rb_player_gst_get_property (GObject *object,
-			    guint prop_id,
-			    GValue *value,
-			    GParamSpec *pspec)
+about_to_finish_cb (GstElement *playbin, RBPlayerGst *player)
 {
-	RBPlayerGst *mp = RB_PLAYER_GST (object);
-
-	switch (prop_id) {
-	case PROP_PLAYBIN:
-		g_value_set_object (value, mp->priv->playbin);
-		break;
-	case PROP_BUS:
-		if (mp->priv->playbin) {
-			GstBus *bus;
-			bus = gst_element_get_bus (mp->priv->playbin);
-			g_value_set_object (value, bus);
-			gst_object_unref (bus);
-		}
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
+	if (player->priv->stream_change_pending == TRUE) {
+		/* this probably shouldn't happen, but it's OK if it does, I think */
+		rb_debug ("got about-to-finish, but we already have a stream change pending.");
+		return;
 	}
-}
 
-static void
-rb_player_gst_finalize (GObject *object)
-{
-	RBPlayerGst *mp;
-
-	mp = RB_PLAYER_GST (object);
-
-	if (mp->priv->tick_timeout_id != 0)
-		g_source_remove (mp->priv->tick_timeout_id);
-
-	if (mp->priv->playbin) {
-		gst_element_set_state (mp->priv->playbin,
-				       GST_STATE_NULL);
-
-		rb_player_gst_gst_free_playbin (mp);
-	}
-	if (mp->priv->volume_handler) {
-		g_object_unref (mp->priv->volume_handler);
-		mp->priv->volume_handler = NULL;
+	/* don't handle about-to-finish for cdda */
+	if (g_str_has_prefix (player->priv->uri, "cdda://")) {
+		rb_debug ("ignoring about-to-finish for %s", player->priv->uri);
+		return;
 	}
 
-	if (mp->priv->waiting_tees) {
-		g_list_foreach (mp->priv->waiting_tees, (GFunc)gst_object_sink, NULL);
-	}
-	g_list_free (mp->priv->waiting_tees);
+	/* emit EOS now and hope we get something to play */
+	player->priv->current_track_finishing = TRUE;
+
+	_rb_player_emit_eos (RB_PLAYER (player), player->priv->stream_data);
+}
 
-	G_OBJECT_CLASS (rb_player_gst_parent_class)->finalize (object);
+static gboolean
+emit_volume_changed_idle (RBPlayerGst *player)
+{
+	_rb_player_emit_volume_changed (RB_PLAYER (player), player->priv->cur_volume);
+	return FALSE;
 }
 
 static void
-rb_player_gst_gst_free_playbin (RBPlayerGst *player)
+volume_notify_cb (GObject *element, GParamSpec *pspec, RBPlayerGst *player)
 {
-	if (player->priv->playbin == NULL)
-		return;
+	gdouble v;
+	g_object_get (element, "volume", &v, NULL);
+	player->priv->cur_volume = v;
 
-	gst_object_unref (GST_OBJECT (player->priv->playbin));
-	player->priv->playbin = NULL;
+	g_idle_add ((GSourceFunc) emit_volume_changed_idle, player);
 }
 
 static void
@@ -302,7 +189,7 @@ process_tag (const GstTagList *list, const gchar *tag, RBPlayerGst *player)
 }
 
 static void
-rb_player_gst_handle_missing_plugin_message (RBPlayerGst *player, GstMessage *message)
+handle_missing_plugin_message (RBPlayerGst *player, GstMessage *message)
 {
 	char **details;
 	char **descriptions;
@@ -334,8 +221,9 @@ rb_player_gst_handle_missing_plugin_message (RBPlayerGst *player, GstMessage *me
 }
 
 static gboolean
-rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp)
+bus_cb (GstBus *bus, GstMessage *message, RBPlayerGst *mp)
 {
+	const GstStructure *structure;
 	g_return_val_if_fail (mp != NULL, FALSE);
 
 	switch (GST_MESSAGE_TYPE (message)) {
@@ -381,9 +269,12 @@ rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp)
 		g_free (debug);
 		break;
 	}
+
 	case GST_MESSAGE_EOS:
+		rb_debug ("got EOS.. why haven't you told me what to do yet?");
 		_rb_player_emit_eos (RB_PLAYER (mp), mp->priv->stream_data);
 		break;
+
 	case GST_MESSAGE_TAG: {
 		GstTagList *tags;
 		gst_message_parse_tag (message, &tags);
@@ -392,12 +283,12 @@ rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp)
 		gst_tag_list_free (tags);
 		break;
 	}
+
 	case GST_MESSAGE_BUFFERING: {
-		const GstStructure *s;
 		gint progress;
 
-		s = gst_message_get_structure (message);
-		if (!gst_structure_get_int (s, "buffer-percent", &progress)) {
+		structure = gst_message_get_structure (message);
+		if (!gst_structure_get_int (structure, "buffer-percent", &progress)) {
 			g_warning ("Could not get value from BUFFERING message");
 			break;
 		}
@@ -422,26 +313,21 @@ rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp)
 			mp->priv->buffering = TRUE;
 		}
 
-		_rb_player_emit_buffering (RB_PLAYER (mp),
-					   mp->priv->stream_data,
-					   progress);
+		_rb_player_emit_buffering (RB_PLAYER (mp), mp->priv->stream_data, progress);
 		break;
 	}
-	case GST_MESSAGE_APPLICATION: {
-		const GstStructure *structure;
 
+	case GST_MESSAGE_APPLICATION:
 		structure = gst_message_get_structure (message);
-		_rb_player_emit_event (RB_PLAYER (mp),
-				       mp->priv->stream_data,
-				       gst_structure_get_name (structure),
-				       NULL);
-	}
-	case GST_MESSAGE_ELEMENT: {
+		_rb_player_emit_event (RB_PLAYER (mp), mp->priv->stream_data, gst_structure_get_name (structure), NULL);
+		break;
+
+	case GST_MESSAGE_ELEMENT:
 		if (gst_is_missing_plugin_message (message)) {
-			rb_player_gst_handle_missing_plugin_message (mp, message);
+			handle_missing_plugin_message (mp, message);
 		}
 		break;
-	}
+
 	default:
 		break;
 	}
@@ -452,41 +338,84 @@ rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp)
 	return TRUE;
 }
 
+static void
+cdda_got_source_cb (GObject *object, GParamSpec *pspec, char *device)
+{
+	GstElement *source;
+
+	g_object_get (object, "source", &source, NULL);
+	rb_debug ("got source %p", source);
+	if (source) {
+		g_signal_handlers_disconnect_by_func (object, cdda_got_source_cb, device);
+
+		g_object_set (G_OBJECT (source), "device", device, NULL);
+		g_free (device);
+
+		if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "paranoia-mode"))
+			g_object_set (G_OBJECT (source), "paranoia-mode", 0, NULL);
+
+		if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "read-speed"))
+			g_object_set (G_OBJECT (source), "read-speed", 1, NULL);
+	}
+}
+
 static gboolean
-rb_player_gst_construct (RBPlayerGst *mp, GError **error)
+construct_pipeline (RBPlayerGst *mp, GError **error)
 {
-	char *element_name = NULL;
 	GstElement *sink;
-	GstElement *fakesink;
 
-	/* playbin */
-	rb_debug ("constructing element \"playbin\"");
-	mp->priv->playbin = gst_element_factory_make ("playbin", "playbin");
+	mp->priv->playbin = gst_element_factory_make ("playbin2", NULL);
 	if (mp->priv->playbin == NULL) {
-		goto missing_element;
+		g_set_error (error,
+			     RB_PLAYER_ERROR,
+			     RB_PLAYER_ERROR_GENERAL,
+			     _("Failed to create playbin2 element; check your GStreamer installation"));
+		return FALSE;
+	}
+	g_signal_connect_object (G_OBJECT (mp->priv->playbin),
+				 "about-to-finish",
+				 G_CALLBACK (about_to_finish_cb),
+				 mp, 0);
+	g_signal_connect_object (G_OBJECT (mp->priv->playbin),
+				 "notify::volume",
+				 G_CALLBACK (volume_notify_cb),
+				 mp, 0);
+	if (mp->priv->buffer_size != 0) {
+		g_object_set (mp->priv->playbin, "buffer-size", mp->priv->buffer_size * 1024, NULL);
 	}
 
-	gst_bus_add_watch (gst_element_get_bus (GST_ELEMENT (mp->priv->playbin)),
-			     (GstBusFunc) rb_player_gst_bus_cb, mp);
+	gst_bus_add_watch (gst_element_get_bus (mp->priv->playbin),
+			   (GstBusFunc) bus_cb,
+			   mp);
 
 	/* let plugins add bits to playbin */
 	g_object_notify (G_OBJECT (mp), "playbin");
 	g_object_notify (G_OBJECT (mp), "bus");
 
 	/* Use gconfaudiosink for audio if there's no audio sink yet */
-	g_object_get (G_OBJECT (mp->priv->playbin), "audio-sink", &sink, NULL);
-	if (sink == NULL) {
-		sink = rb_player_gst_try_audio_sink ("gconfaudiosink", "audiosink");
-		if (sink == NULL) {
+	g_object_get (mp->priv->playbin, "audio-sink", &mp->priv->audio_sink, NULL);
+	if (mp->priv->audio_sink == NULL) {
+		mp->priv->audio_sink = gst_element_factory_make ("gconfaudiosink", NULL);
+		if (mp->priv->audio_sink == NULL) {
 			/* fall back to autoaudiosink */
-			sink = rb_player_gst_try_audio_sink ("autoaudiosink", "audiosink");
+			rb_debug ("falling back to autoaudiosink");
+			mp->priv->audio_sink = gst_element_factory_make ("autoaudiosink", NULL);
+		} else {
+			rb_debug ("using gconfaudiosink");
 		}
 
-		if (sink != NULL) {
-			g_object_set (G_OBJECT (mp->priv->playbin), "audio-sink", sink, NULL);
+		if (mp->priv->audio_sink != NULL) {
+			/* set the profile property on the gconfaudiosink to "music and movies" */
+			if (g_object_class_find_property (G_OBJECT_GET_CLASS (mp->priv->audio_sink), "profile")) {
+				rb_debug ("setting profile property on audio sink");
+				g_object_set (mp->priv->audio_sink, "profile", 1, NULL);
+			}
+
+			g_object_set (mp->priv->playbin, "audio-sink", mp->priv->audio_sink, NULL);
 		}
 	} else {
-		g_object_unref (sink);
+		rb_debug ("existing audio sink found");
+		g_object_unref (mp->priv->audio_sink);
 	}
 
 	{
@@ -502,27 +431,26 @@ rb_player_gst_construct (RBPlayerGst *mp, GError **error)
 		gst_bin_add (GST_BIN (mp->priv->filterbin), audioconvert);
 
 		/* ghost it to the bin */
-		pad = gst_element_get_static_pad (audioconvert, "sink");
+		pad = gst_element_get_pad (audioconvert, "sink");
 		ghostpad = gst_ghost_pad_new ("sink", pad);
 		gst_element_add_pad (mp->priv->filterbin, ghostpad);
 		gst_object_unref (pad);
 
-		pad = gst_element_get_static_pad (audioconvert, "src");
+		pad = gst_element_get_pad (audioconvert, "src");
 		ghostpad = gst_ghost_pad_new ("src", pad);
 		gst_element_add_pad (mp->priv->filterbin, ghostpad);
 		gst_object_unref (pad);
 
-
-		/* set up the sinkbin with it's tee element */
+		/* set up the sinkbin with its tee element */
 		mp->priv->sinkbin = gst_bin_new (NULL);
 		mp->priv->tee = gst_element_factory_make ("tee", NULL);
 		queue = gst_element_factory_make ("queue", NULL);
 
 		/* link it all together and insert */
-		gst_bin_add_many (GST_BIN (mp->priv->sinkbin), mp->priv->filterbin, mp->priv->tee, queue, sink, NULL);
-		gst_element_link_many (mp->priv->filterbin, mp->priv->tee, queue, sink, NULL);
+		gst_bin_add_many (GST_BIN (mp->priv->sinkbin), mp->priv->filterbin, mp->priv->tee, queue, mp->priv->audio_sink, NULL);
+		gst_element_link_many (mp->priv->filterbin, mp->priv->tee, queue, mp->priv->audio_sink, NULL);
 
-		pad = gst_element_get_static_pad (mp->priv->filterbin, "sink");
+		pad = gst_element_get_pad (mp->priv->filterbin, "sink");
 		ghostpad = gst_ghost_pad_new ("sink", pad);
 		gst_element_add_pad (mp->priv->sinkbin, ghostpad);
 		gst_object_unref (pad);
@@ -545,10 +473,10 @@ rb_player_gst_construct (RBPlayerGst *mp, GError **error)
 	}
 
 	/* Use fakesink for video if there's no video sink yet */
-	g_object_get (G_OBJECT (mp->priv->playbin), "video-sink", &sink, NULL);
+	g_object_get (mp->priv->playbin, "video-sink", &sink, NULL);
 	if (sink == NULL) {
-		fakesink = gst_element_factory_make ("fakesink", "fakesink");
-		g_object_set (G_OBJECT (mp->priv->playbin), "video-sink", fakesink, NULL);
+		sink = gst_element_factory_make ("fakesink", NULL);
+		g_object_set (mp->priv->playbin, "video-sink", sink, NULL);
 	} else {
 		g_object_unref (sink);
 	}
@@ -557,215 +485,166 @@ rb_player_gst_construct (RBPlayerGst *mp, GError **error)
 		mp->priv->cur_volume = 1.0;
 	if (mp->priv->cur_volume < 0.0)
 		mp->priv->cur_volume = 0;
+	mp->priv->replaygain_scale = 1.0f;
+
 	rb_player_set_volume (RB_PLAYER (mp), mp->priv->cur_volume);
 
 	rb_debug ("pipeline construction complete");
 	return TRUE;
-missing_element:
-	{
-		char *err = g_strdup_printf (_("Failed to create %s element; check your installation"),
-					     element_name);
-		g_set_error (error,
-			     RB_PLAYER_ERROR,
-			     RB_PLAYER_ERROR_GENERAL,
-			     "%s", err);
-		g_free (err);
-		rb_player_gst_gst_free_playbin (mp);
-		return FALSE;
-	}
 }
 
-RBPlayer *
-rb_player_gst_new (GError **error)
+static gboolean
+message_from_sink (GstElement *sink, GstMessage *message)
 {
-	RBPlayerGst *mp;
-
-	mp = RB_PLAYER_GST (g_object_new (RB_TYPE_PLAYER_GST, NULL, NULL));
+	GstElement *src;
+	GstElement *match;
+	char *name;
 
-	return RB_PLAYER (mp);
-}
+	src = GST_ELEMENT (GST_MESSAGE_SRC (message));
 
-static gboolean
-rb_player_gst_sync_pipeline (RBPlayerGst *mp)
-{
-	rb_debug ("syncing pipeline");
-	if (mp->priv->playing) {
- 		rb_debug ("PLAYING pipeline");
-		if (gst_element_set_state (mp->priv->playbin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
- 			return FALSE;
-		}
-	} else {
-		rb_debug ("PAUSING pipeline");
-		if (gst_element_set_state (mp->priv->playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
-			return FALSE;
-		}
+	if (GST_IS_BIN (sink) == FALSE) {
+		return (src == sink);
 	}
 
-	/* FIXME: Set up a timeout to watch if the pipeline doesn't
-	 * go to PAUSED/PLAYING within some time (5 secs maybe?)
-	 */
-	return TRUE;
-}
+	name = gst_element_get_name (src);
+	match = gst_bin_get_by_name (GST_BIN (sink), name);
+	g_free (name);
 
-/* Start a sequence of synchronous GStreamer operations in which we
- * can receive an error signal.
- */
-static void
-begin_gstreamer_operation (RBPlayerGst *mp)
-{
-	g_assert (mp->priv->error == NULL);
-	mp->priv->can_signal_direct_error = TRUE;
-}
-
-/* End a sequence of synchronous operations and propagate any
- * error from the sequence into error.
- */
-static void
-end_gstreamer_operation (RBPlayerGst *mp, gboolean op_failed, GError **error)
-{
-	mp->priv->can_signal_direct_error = FALSE;
-	if (mp->priv->error) {
-		g_propagate_error (error, mp->priv->error);
-		mp->priv->error = NULL;
-	} else if (op_failed) {
-		g_set_error (error,
-			     RB_PLAYER_ERROR,
-			     RB_PLAYER_ERROR_GENERAL,
-			     _("Unknown playback error"));
+	if (match != NULL) {
+		g_object_unref (match);
+		return (match == src);
 	}
 
+	return FALSE;
 }
 
-static void
-cdda_got_source_cb (GObject *object, GParamSpec *pspec, char *device)
+static gboolean
+set_state_and_wait (RBPlayerGst *player, GstState target, GError **error)
 {
-	GstElement *source;
+	GstBus *bus;
+	gboolean waiting;
+	gboolean result;
 
-	g_object_get (object, "source", &source, NULL);
-	rb_debug ("got source %p", source);
-	if (source) {
-		g_signal_handlers_disconnect_by_func (object, cdda_got_source_cb, device);
+	g_assert (player->priv->playbin != NULL);
+	/* XXX probably need to remove bus watch here if we're not on the main thread */
+	/* .. probably shouldn't be doing this much anyway .. */
 
-		g_object_set (G_OBJECT (source), "device", device, NULL);
-		g_free (device);
+	rb_debug ("setting playbin state to %s", gst_element_state_get_name (target));
 
-		if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "paranoia-mode"))
-			g_object_set (G_OBJECT (source), "paranoia-mode", 0, NULL);
+	switch (gst_element_set_state (player->priv->playbin, target)) {
+	case GST_STATE_CHANGE_SUCCESS:
+		rb_debug ("state change was successful");
+		return TRUE;
 
-		if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "read-speed"))
-			g_object_set (G_OBJECT (source), "read-speed", 1, NULL);
-	}
-}
+	case GST_STATE_CHANGE_NO_PREROLL:
+		rb_debug ("state change was successful (no preroll)");
+		return TRUE;
 
-static void
-_destroy_stream_data (RBPlayerGst *player)
-{
-	if (player->priv->stream_data && player->priv->stream_data_destroy) {
-		player->priv->stream_data_destroy (player->priv->stream_data);
-	}
-	player->priv->stream_data = NULL;
-	player->priv->stream_data_destroy = NULL;
-}
+	case GST_STATE_CHANGE_ASYNC:
+		rb_debug ("state is changing asynchronously");
+		result = TRUE;
+		break;
 
-static gboolean
-rb_player_gst_open (RBPlayer *player,
-		const char *uri,
-		gpointer stream_data,
-		GDestroyNotify stream_data_destroy,
-		GError **error)
-{
-	RBPlayerGst *mp = RB_PLAYER_GST (player);
-	gboolean cdda_seek = FALSE;
+	case GST_STATE_CHANGE_FAILURE:
+		rb_debug ("state change failed");
+		result = FALSE;
+		break;
 
-	if (mp->priv->playbin == NULL) {
-		if (!rb_player_gst_construct (mp, error))
-			return FALSE;
-	} else {
-		if (!rb_player_close (player, NULL, error))
-			return FALSE;
+	default:
+		rb_debug ("unknown state change return..");
+		result = TRUE;
+		break;
 	}
 
-	g_assert (mp->priv->playbin != NULL);
-
-	if (uri == NULL) {
-		_destroy_stream_data (mp);
-		g_free (mp->priv->uri);
-		mp->priv->uri = NULL;
-		mp->priv->playing = FALSE;
-		mp->priv->buffering = FALSE;
-		return TRUE;
-	}
+	bus = gst_element_get_bus (player->priv->playbin);
+	waiting = TRUE;
+	while (waiting) {
+		GstMessage *message;
 
-	/* check if we are switching tracks on a cd, so we don't have to close the device */
-	if (g_str_has_prefix (uri, "cdda://")) {
-		const char *old_device = NULL;
-		const char *new_device;
-
-		if (mp->priv->uri && g_str_has_prefix (mp->priv->uri, "cdda://"))
-			old_device = g_utf8_strchr (mp->priv->uri, -1, '#');
-		new_device = g_utf8_strchr (uri, -1, '#');
-
-		if (old_device && strcmp (old_device, new_device) == 0) {
-			/* just seek, instead of having playbin close the device */
-			GstFormat track_format = gst_format_get_by_nick ("track");
-			char *track_str;
-			guint track;
-			guint cdda_len;
-
-			cdda_len = strlen ("cdda://");
-			track_str = g_strndup (uri + cdda_len, new_device - (uri + cdda_len));
-			track = atoi (track_str);
-			g_free (track_str);
-
-			rb_debug ("seeking to track %d on CD device %s", track, new_device);
-
-			if (gst_element_seek (mp->priv->playbin, 1.0,
-					      track_format, GST_SEEK_FLAG_FLUSH,
-					      GST_SEEK_TYPE_SET, track,
-					      GST_SEEK_TYPE_NONE, -1))
-				cdda_seek = TRUE;
-		} else {
-			/* +1 to skip the '#' */
-			char *device = g_strdup (new_device + 1);
-
-			rb_debug ("waiting for source element for CD device %s", device);
-			g_signal_connect (G_OBJECT (mp->priv->playbin),
-					  "notify::source",
-					  G_CALLBACK (cdda_got_source_cb),
-					  device);
+		message = gst_bus_timed_pop (bus, GST_SECOND * STATE_CHANGE_MESSAGE_TIMEOUT);
+		if (message == NULL) {
+			rb_debug ("state change is taking too long..");
+			break;
 		}
-	}
 
-	begin_gstreamer_operation (mp);
-	_destroy_stream_data (mp);
-	g_free (mp->priv->uri);
-	mp->priv->uri = g_strdup (uri);
-	mp->priv->stream_data = stream_data;
-	mp->priv->stream_data_destroy = stream_data_destroy;
-	mp->priv->emitted_error = FALSE;
+		switch (GST_MESSAGE_TYPE (message)) {
+		case GST_MESSAGE_ERROR:
+			{
+				char *debug;
+				GError *gst_error = NULL;
+
+				gst_message_parse_error (message, &gst_error, &debug);
+
+				if (message_from_sink (player->priv->audio_sink, message)) {
+					rb_debug ("got error from sink: %s (%s)", gst_error->message, debug);
+					g_set_error (error,
+						     RB_PLAYER_ERROR,
+						     RB_PLAYER_ERROR_INTERNAL,
+						     _("Failed to open output device: %s"),
+						     gst_error->message);
+				} else {
+					rb_debug ("got error from stream: %s (%s)", gst_error->message, debug);
+					g_set_error (error,
+						     RB_PLAYER_ERROR,
+						     RB_PLAYER_ERROR_GENERAL,
+						     "%s",
+						     gst_error->message);
+				}
+
+				g_error_free (gst_error);
+				g_free (debug);
+
+				waiting = FALSE;
+				result = FALSE;
+				break;
+			}
+
+		case GST_MESSAGE_STATE_CHANGED:
+			{
+				GstState oldstate;
+				GstState newstate;
+				GstState pending;
+				gst_message_parse_state_changed (message, &oldstate, &newstate, &pending);
+				if (GST_MESSAGE_SRC (message) == GST_OBJECT (player->priv->playbin)) {
+					rb_debug ("playbin reached state %s", gst_element_state_get_name (newstate));
+					if (pending == GST_STATE_VOID_PENDING && newstate == target) {
+						waiting = FALSE;
+					}
+				}
+				break;
+			}
 
-	if (!cdda_seek) {
-		g_object_set (G_OBJECT (mp->priv->playbin), "uri", uri, NULL);
+		default:
+			/* pass back to regular message handler */
+			bus_cb (bus, message, player);
+			break;
+		}
 	}
 
-	if (!rb_player_gst_sync_pipeline (mp)) {
-		end_gstreamer_operation (mp, TRUE, error);
-		rb_player_gst_close (player, uri, NULL);
-		return FALSE;
+	if (result == FALSE && *error == NULL) {
+		g_set_error (error,
+			     RB_PLAYER_ERROR,
+			     RB_PLAYER_ERROR_GENERAL,
+			     _("Unable to start playback pipeline"));
 	}
 
-	if (mp->priv->tick_timeout_id == 0)
-		mp->priv->tick_timeout_id = g_timeout_add (1000 / RB_PLAYER_GST_TICK_HZ, (GSourceFunc) tick_timeout, mp);
+	return result;
+}
 
-	end_gstreamer_operation (mp, FALSE, error);
-	return TRUE;
+static void
+_destroy_stream_data (RBPlayerGst *player)
+{
+	if (player->priv->stream_data && player->priv->stream_data_destroy) {
+		player->priv->stream_data_destroy (player->priv->stream_data);
+	}
+	player->priv->stream_data = NULL;
+	player->priv->stream_data_destroy = NULL;
 }
 
 static gboolean
-rb_player_gst_close (RBPlayer *player, const char *uri, GError **error)
+impl_close (RBPlayer *player, const char *uri, GError **error)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
-	gboolean ret;
 
 	mp->priv->playing = FALSE;
 	mp->priv->buffering = FALSE;
@@ -777,7 +656,11 @@ rb_player_gst_close (RBPlayer *player, const char *uri, GError **error)
 
 	_destroy_stream_data (mp);
 	g_free (mp->priv->uri);
+	g_free (mp->priv->prev_uri);
 	mp->priv->uri = NULL;
+	mp->priv->prev_uri = NULL;
+
+	mp->priv->replaygain_scale = 1.0f;
 
 	if (mp->priv->tick_timeout_id != 0) {
 		g_source_remove (mp->priv->tick_timeout_id);
@@ -787,20 +670,44 @@ rb_player_gst_close (RBPlayer *player, const char *uri, GError **error)
 	if (mp->priv->playbin == NULL)
 		return TRUE;
 
-	begin_gstreamer_operation (mp);
-	ret = gst_element_set_state (mp->priv->playbin, GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS;
-	end_gstreamer_operation (mp, !ret, error);
+	return set_state_and_wait (mp, GST_STATE_READY, error);
+}
+
+static gboolean
+impl_open (RBPlayer *player,
+	   const char *uri,
+	   gpointer stream_data,
+	   GDestroyNotify stream_data_destroy,
+	   GError **error)
+{
+	RBPlayerGst *mp = RB_PLAYER_GST (player);
 
-	if (mp->priv->volume_handler) {
-		g_object_unref (mp->priv->volume_handler);
-		mp->priv->volume_handler = NULL;
+	if (mp->priv->playbin == NULL) {
+		if (!construct_pipeline (mp, error))
+			return FALSE;
 	}
 
-	return ret;
+	g_assert (mp->priv->playbin != NULL);
+
+	if (uri == NULL) {
+		return impl_close (player, NULL, error);
+	}
+
+	rb_debug ("setting new uri to %s", uri);
+	_destroy_stream_data (mp);
+	g_free (mp->priv->prev_uri);
+	mp->priv->prev_uri = mp->priv->uri;
+	mp->priv->uri = g_strdup (uri);
+	mp->priv->stream_data = stream_data;
+	mp->priv->stream_data_destroy = stream_data_destroy;
+	mp->priv->emitted_error = FALSE;
+	mp->priv->stream_change_pending = TRUE;
+
+	return TRUE;
 }
 
 static gboolean
-rb_player_gst_opened (RBPlayer *player)
+impl_opened (RBPlayer *player)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
 
@@ -808,32 +715,105 @@ rb_player_gst_opened (RBPlayer *player)
 }
 
 static gboolean
-rb_player_gst_play (RBPlayer *player, gint crossfade, GError **error)
+impl_play (RBPlayer *player, gint crossfade, GError **error)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
-	gboolean ret;
-
-	mp->priv->playing = TRUE;
-	mp->priv->buffering = FALSE;
+	gboolean result;
 
 	g_return_val_if_fail (mp->priv->playbin != NULL, FALSE);
 
-	begin_gstreamer_operation (mp);
-	ret = rb_player_gst_sync_pipeline (mp);
-	end_gstreamer_operation (mp, !ret, error);
+	if (mp->priv->stream_change_pending == FALSE) {
+		rb_debug ("no stream change pending, just restarting playback");
+		result = set_state_and_wait (mp, GST_STATE_PLAYING, error);
 
-	_rb_player_emit_playing_stream (RB_PLAYER (mp), mp->priv->stream_data);
+	} else if (mp->priv->current_track_finishing) {
+		rb_debug ("current track finishing -> just setting URI on playbin");
+		g_object_set (mp->priv->playbin, "uri", mp->priv->uri, NULL);
 
-	if (mp->priv->tick_timeout_id == 0)
-		mp->priv->tick_timeout_id = g_timeout_add (1000 / RB_PLAYER_GST_TICK_HZ, (GSourceFunc) tick_timeout, mp);
+		_rb_player_emit_playing_stream (RB_PLAYER (mp), mp->priv->stream_data);
 
-	return ret;
+		result = TRUE;
+	} else {
+		gboolean cdda_seek = FALSE;
+
+		rb_debug ("not in transition, stopping current track to start the new one");
+
+		/* check if we are switching tracks on a cd, so we don't have to close the device */
+		if (g_str_has_prefix (mp->priv->uri, "cdda://")) {
+			const char *old_device = NULL;
+			const char *new_device;
+
+			if (mp->priv->prev_uri && g_str_has_prefix (mp->priv->prev_uri, "cdda://"))
+				old_device = g_utf8_strchr (mp->priv->prev_uri, -1, '#');
+
+			new_device = g_utf8_strchr (mp->priv->uri, -1, '#');
+
+			if (old_device && strcmp (old_device, new_device) == 0) {
+				GstFormat track_format = gst_format_get_by_nick ("track");
+				char *track_str;
+				guint track;
+				guint cdda_len;
+
+				cdda_len = strlen ("cdda://");
+				track_str = g_strndup (mp->priv->uri + cdda_len, new_device - (mp->priv->uri + cdda_len));
+				track = atoi (track_str);
+				g_free (track_str);
+
+				rb_debug ("seeking to track %d on CD device %s", track, new_device);
+				if (gst_element_seek (mp->priv->playbin, 1.0,
+						      track_format, GST_SEEK_FLAG_FLUSH,
+						      GST_SEEK_TYPE_SET, track - 1,
+						      GST_SEEK_TYPE_NONE, -1)) {
+					cdda_seek = TRUE;
+					result = TRUE;
+					_rb_player_emit_playing_stream (RB_PLAYER (mp), mp->priv->stream_data);
+				}
+			} else {
+				/* +1 to skip the '#' */
+				char *device = g_strdup (new_device + 1);
+
+				rb_debug ("waiting for source element for CD device %s", device);
+				g_signal_connect (G_OBJECT (mp->priv->playbin),
+						  "notify::source",
+						  G_CALLBACK (cdda_got_source_cb),
+						  device);
+			}
+		}
+
+		if (cdda_seek == FALSE) {
+			result = set_state_and_wait (mp, GST_STATE_READY, error);
+			if (result == TRUE) {
+				g_object_set (mp->priv->playbin, "uri", mp->priv->uri, NULL);
+
+				_rb_player_emit_playing_stream (RB_PLAYER (mp), mp->priv->stream_data);
+				result = set_state_and_wait (mp, GST_STATE_PLAYING, error);
+			}
+		}
+	}
+
+	mp->priv->stream_change_pending = FALSE;
+
+	if (result) {
+		mp->priv->current_track_finishing = FALSE;
+		mp->priv->buffering = FALSE;
+		mp->priv->playing = TRUE;
+
+		if (mp->priv->tick_timeout_id == 0) {
+			mp->priv->tick_timeout_id =
+				g_timeout_add (1000 / RB_PLAYER_GST_TICK_HZ,
+					       (GSourceFunc) tick_timeout,
+					       mp);
+		}
+	}
+
+	return result;
 }
 
 static void
-rb_player_gst_pause (RBPlayer *player)
+impl_pause (RBPlayer *player)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
+	GError *error = NULL;
 
 	if (!mp->priv->playing)
 		return;
@@ -842,7 +822,10 @@ rb_player_gst_pause (RBPlayer *player)
 
 	g_return_if_fail (mp->priv->playbin != NULL);
 
-	rb_player_gst_sync_pipeline (mp);
+	if (set_state_and_wait (mp, GST_STATE_PAUSED, &error) == FALSE) {
+		g_warning ("unable to pause playback: %s\n", error->message);
+		g_error_free (error);
+	}
 
 	if (mp->priv->tick_timeout_id != 0) {
 		g_source_remove (mp->priv->tick_timeout_id);
@@ -851,59 +834,20 @@ rb_player_gst_pause (RBPlayer *player)
 }
 
 static gboolean
-rb_player_gst_playing (RBPlayer *player)
+impl_playing (RBPlayer *player)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
 
 	return mp->priv->playing;
 }
 
-static gboolean
-emit_volume_changed_idle (RBPlayerGst *mp)
-{
-	_rb_player_emit_volume_changed (RB_PLAYER (mp), mp->priv->cur_volume);
-	return FALSE;
-}
-
-static void
-stream_volume_changed (GObject *element, GParamSpec *pspec, RBPlayerGst *mp)
-{
-	gdouble v;
-	g_object_get (element, "volume", &v, NULL);
-	mp->priv->cur_volume = v;
-
-	g_idle_add ((GSourceFunc) emit_volume_changed_idle, mp);
-}
-
 static void
-find_volume_handler (RBPlayerGst *mp)
-{
-	/* look for a 'volume' property provided by the sink */
-	if (mp->priv->volume_handler == NULL && mp->priv->playbin != NULL) {
-		GstElement *sink;
-
-		g_object_get (mp->priv->playbin, "audio-sink", &sink, NULL);
-		if (sink != NULL) {
-			mp->priv->volume_handler = rb_player_gst_find_element_with_property (sink, "volume");
-			g_object_unref (sink);
-		}
-
-		if (mp->priv->volume_handler == NULL) {
-			mp->priv->volume_handler = g_object_ref (mp->priv->playbin);
-		}
-
-		g_signal_connect_object (mp->priv->volume_handler,
-					 "notify::volume",
-					 G_CALLBACK (stream_volume_changed),
-					 mp, 0);
-	}
-}
-
-static void
-rb_player_gst_set_replaygain (RBPlayer *player,
-			      const char *uri,
-			      double track_gain, double track_peak,
-			      double album_gain, double album_peak)
+impl_set_replaygain (RBPlayer *player,
+		     const char *uri,
+		     double track_gain,
+		     double track_peak,
+		     double album_gain,
+		     double album_peak)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
 	double scale;
@@ -911,9 +855,9 @@ rb_player_gst_set_replaygain (RBPlayer *player,
 	double peak = 0;
 
 	if (album_gain != 0)
-	  gain = album_gain;
+		gain = album_gain;
 	else
-	  gain = track_gain;
+		gain = track_gain;
 
 	if (gain == 0)
 		return;
@@ -922,9 +866,9 @@ rb_player_gst_set_replaygain (RBPlayer *player,
 
 	/* anti clip */
 	if (album_peak != 0)
-	  peak = album_peak;
+		peak = album_peak;
 	else
-	  peak = track_peak;
+		peak = track_peak;
 
 	if (peak != 0 && (scale * peak) > 1)
 		scale = 1.0 / peak;
@@ -934,42 +878,29 @@ rb_player_gst_set_replaygain (RBPlayer *player,
 		scale = 15;
 
 	rb_debug ("Scale : %f New volume : %f", scale, mp->priv->cur_volume * scale);
+	mp->priv->replaygain_scale = scale;
 
-	find_volume_handler (mp);
-	if (mp->priv->volume_handler != NULL) {
-		GParamSpec *volume_pspec;
-		GValue val = {0,};
-
-		volume_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (mp->priv->volume_handler),
-							     "volume");
-		g_value_init (&val, G_TYPE_DOUBLE);
-
-		g_value_set_double (&val, mp->priv->cur_volume * scale);
-		if (g_param_value_validate (volume_pspec, &val))
-			rb_debug ("replay gain too high, reducing value to %f", g_value_get_double (&val));
-
-		g_object_set_property (G_OBJECT (mp->priv->volume_handler), "volume", &val);
-		g_value_unset (&val);
+	if (mp->priv->playbin != NULL) {
+		g_object_set (mp->priv->playbin, "volume", mp->priv->cur_volume * scale, NULL);
 	}
 }
 
 static void
-rb_player_gst_set_volume (RBPlayer *player,
-			  float volume)
+impl_set_volume (RBPlayer *player,
+		 float volume)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
 	g_return_if_fail (volume >= 0.0 && volume <= 1.0);
 
-	find_volume_handler (mp);
-	if (mp->priv->volume_handler != NULL) {
-		g_object_set (mp->priv->volume_handler, "volume", volume, NULL);
+	if (mp->priv->playbin != NULL) {
+		g_object_set (mp->priv->playbin, "volume", volume * mp->priv->replaygain_scale, NULL);
 	}
 
 	mp->priv->cur_volume = volume;
 }
 
 static float
-rb_player_gst_get_volume (RBPlayer *player)
+impl_get_volume (RBPlayer *player)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
 
@@ -977,10 +908,9 @@ rb_player_gst_get_volume (RBPlayer *player)
 }
 
 static gboolean
-rb_player_gst_seekable (RBPlayer *player)
+impl_seekable (RBPlayer *player)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
-	/* Need to send a seekable query on playbin */
 	gboolean can_seek = TRUE;
 	GstQuery *query;
 
@@ -1002,18 +932,20 @@ rb_player_gst_seekable (RBPlayer *player)
 }
 
 static void
-rb_player_gst_set_time (RBPlayer *player, long time)
+impl_set_time (RBPlayer *player, long time)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
+	GError *error = NULL;
+
 	g_return_if_fail (time >= 0);
 
 	g_return_if_fail (mp->priv->playbin != NULL);
 
-	if (gst_element_set_state (mp->priv->playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC) {
-		/* FIXME: Use a timeout on get_state. Post a GError somewhere on failed? */
-		if (gst_element_get_state (mp->priv->playbin, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-			g_warning ("Failed to pause pipeline before seek");
-		}
+	if (set_state_and_wait (mp, GST_STATE_PAUSED, &error) == FALSE) {
+		g_warning ("got error while pausing the pipelink for seeking: %s\n", error->message);
+		g_clear_error (&error);
+
+		/* keep going anyway? */
 	}
 
 	gst_element_seek (mp->priv->playbin, 1.0,
@@ -1022,12 +954,15 @@ rb_player_gst_set_time (RBPlayer *player, long time)
 			  GST_SEEK_TYPE_NONE, -1);
 
 	if (mp->priv->playing) {
-		gst_element_set_state (mp->priv->playbin, GST_STATE_PLAYING);
+		if (set_state_and_wait (mp, GST_STATE_PLAYING, &error) == FALSE) {
+			g_warning ("unable to resume playback after seeking: %s\n", error->message);
+			g_clear_error (&error);
+		}
 	}
 }
 
 static long
-rb_player_gst_get_time (RBPlayer *player)
+impl_get_time (RBPlayer *player)
 {
 	RBPlayerGst *mp = RB_PLAYER_GST (player);
 
@@ -1040,13 +975,13 @@ rb_player_gst_get_time (RBPlayer *player)
 			position /= GST_SECOND;
 
 		return (long) position;
-	} else
+	} else {
 		return -1;
+	}
 }
 
-
 static gboolean
-rb_player_gst_add_tee (RBPlayerGstTee *player, GstElement *element)
+impl_add_tee (RBPlayerGstTee *player, GstElement *element)
 {
 	RBPlayerGst *mp;
 	GstElement *queue, *audioconvert, *bin;
@@ -1060,12 +995,11 @@ rb_player_gst_add_tee (RBPlayerGstTee *player, GstElement *element)
 	}
 
 	if (mp->priv->playing) {
-		if (gst_element_set_state (mp->priv->playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC) {
-			/* FIXME: Use a timeout on get_state. Post a GError somewhere on failed? */
-			if (gst_element_get_state (mp->priv->playbin, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-				g_warning ("Failed to pause pipeline before tee insertion");
-				return FALSE;
-			}
+		GError *error = NULL;
+		if (set_state_and_wait (mp, GST_STATE_PAUSED, &error) == FALSE) {
+			g_warning ("Failed to pause pipeline before tee insertion: %s", error->message);
+			g_error_free (error);
+			return FALSE;
 		}
 	}
 
@@ -1079,7 +1013,7 @@ rb_player_gst_add_tee (RBPlayerGstTee *player, GstElement *element)
 	gst_element_link_many (queue, audioconvert, element, NULL);
 
 	/* link it to the tee */
-	pad = gst_element_get_static_pad (queue, "sink");
+	pad = gst_element_get_pad (queue, "sink");
 	ghostpad = gst_ghost_pad_new ("sink", pad);
 	gst_element_add_pad (bin, ghostpad);
 	gst_object_unref (pad);
@@ -1095,7 +1029,7 @@ rb_player_gst_add_tee (RBPlayerGstTee *player, GstElement *element)
 }
 
 static gboolean
-rb_player_gst_remove_tee (RBPlayerGstTee *player, GstElement *element)
+impl_remove_tee (RBPlayerGstTee *player, GstElement *element)
 {
 	RBPlayerGst *mp;
 	GstElement *bin;
@@ -1111,25 +1045,18 @@ rb_player_gst_remove_tee (RBPlayerGstTee *player, GstElement *element)
 	_rb_player_gst_tee_emit_tee_pre_remove (player, element);
 
 	if (mp->priv->playing) {
-		if (gst_element_set_state (mp->priv->playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC) {
-			/* FIXME: Use a timeout on get_state. Post a GError somewhere on failed? */
-			if (gst_element_get_state (mp->priv->playbin, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-				g_warning ("Failed to pause pipeline before eee insertion");
-				return FALSE;
-			}
+		GError *error = NULL;
+		if (set_state_and_wait (mp, GST_STATE_PAUSED, &error) == FALSE) {
+			g_warning ("Failed to pause pipeline before tee removal: %s", error->message);
+			g_error_free (error);
+			return FALSE;
 		}
 	}
 
 	/* get the containing bin and unlink it */
 	bin = GST_ELEMENT (gst_element_get_parent (element));
 
-	if (gst_element_set_state (bin, GST_STATE_NULL) == GST_STATE_CHANGE_ASYNC) {
-		/* FIXME: Use a timeout on get_state. Post a GError somewhere on failed? */
-		if (gst_element_get_state (bin, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-			g_warning ("Failed to pause pipeline before tee insertion");
-			return FALSE;
-		}
-	}
+	gst_element_set_state (bin, GST_STATE_NULL);
 
 	gst_bin_remove (GST_BIN (mp->priv->sinkbin), bin);
 	gst_object_unref (bin);
@@ -1141,7 +1068,7 @@ rb_player_gst_remove_tee (RBPlayerGstTee *player, GstElement *element)
 }
 
 static gboolean
-rb_player_gst_add_filter (RBPlayerGstFilter *player, GstElement *element)
+impl_add_filter (RBPlayerGstFilter *player, GstElement *element)
 {
 	RBPlayerGst *mp;
 	GstElement *audioconvert, *bin;
@@ -1160,12 +1087,11 @@ rb_player_gst_add_filter (RBPlayerGstFilter *player, GstElement *element)
 	}
 
 	if (mp->priv->playing) {
-		if (gst_element_set_state (mp->priv->playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC) {
-			/* FIXME: Use a timeout on get_state. Post a GError somewhere on failed? */
-			if (gst_element_get_state (mp->priv->playbin, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-				g_warning ("Failed to pause pipeline before filter insertion");
-				return FALSE;
-			}
+		GError *error = NULL;
+		if (set_state_and_wait (mp, GST_STATE_PAUSED, &error) == FALSE) {
+			g_warning ("Failed to pause pipeline before filter insertion: %s", error->message);
+			g_error_free (error);
+			return FALSE;
 		}
 	}
 
@@ -1207,7 +1133,7 @@ rb_player_gst_add_filter (RBPlayerGstFilter *player, GstElement *element)
 	binsinkpad = gst_ghost_pad_new ("sink", GST_PAD (element_sink_pad));
 	gst_element_add_pad (bin, binsinkpad);
 
-	realpad = gst_element_get_static_pad (audioconvert, "src");
+	realpad = gst_element_get_pad (audioconvert, "src");
 	binsrcpad = gst_ghost_pad_new ("src", realpad);
 	gst_element_add_pad (bin, binsrcpad);
 	gst_object_unref (realpad);
@@ -1215,7 +1141,7 @@ rb_player_gst_add_filter (RBPlayerGstFilter *player, GstElement *element)
 	/* replace the filter chain ghost with the new bin */
 	gst_bin_add (GST_BIN (mp->priv->filterbin), bin);
 
-	ghostpad = gst_element_get_static_pad (mp->priv->filterbin, "src");
+	ghostpad = gst_element_get_pad (mp->priv->filterbin, "src");
 	realpad = gst_ghost_pad_get_target (GST_GHOST_PAD (ghostpad));
 	gst_ghost_pad_set_target (GST_GHOST_PAD (ghostpad), binsrcpad);
 	gst_object_unref (ghostpad);
@@ -1236,7 +1162,7 @@ rb_player_gst_add_filter (RBPlayerGstFilter *player, GstElement *element)
 }
 
 static gboolean
-rb_player_gst_remove_filter (RBPlayerGstFilter *player, GstElement *element)
+impl_remove_filter (RBPlayerGstFilter *player, GstElement *element)
 {
 	RBPlayerGst *mp;
 	GstPad *mypad;
@@ -1257,35 +1183,26 @@ rb_player_gst_remove_filter (RBPlayerGstFilter *player, GstElement *element)
 	_rb_player_gst_filter_emit_filter_pre_remove (player, element);
 
 	if (mp->priv->playing) {
-		/* it'd be more fun to do this by blocking a pad.. */
-
-		if (gst_element_set_state (mp->priv->playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC) {
-			/* FIXME: Use a timeout on get_state. Post a GError somewhere on failed? */
-			if (gst_element_get_state (mp->priv->playbin, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-				g_warning ("Failed to pause pipeline before filter insertion");
-				return FALSE;
-			}
+		GError *error = NULL;
+		if (set_state_and_wait (mp, GST_STATE_PAUSED, &error) == FALSE) {
+			g_warning ("Failed to pause pipeline before filter removal: %s", error->message);
+			g_error_free (error);
+			return FALSE;
 		}
 	}
 
 	/* get the containing bin and unlink it */
 	bin = GST_ELEMENT (gst_element_get_parent (element));
 
-	if (gst_element_set_state (bin, GST_STATE_NULL) == GST_STATE_CHANGE_ASYNC) {
-		/* FIXME: Use a timeout on get_state. Post a GError somewhere on failed? */
-		if (gst_element_get_state (bin, NULL, NULL, 3 * GST_SECOND) != GST_STATE_CHANGE_SUCCESS) {
-			g_warning ("Failed to pause pipeline before filter insertion");
-			return FALSE;
-		}
-	}
+	gst_element_set_state (bin, GST_STATE_NULL);
 
-	mypad = gst_element_get_static_pad (bin, "sink");
+	mypad = gst_element_get_pad (bin, "sink");
 	prevpad = gst_pad_get_peer (mypad);
 	gst_pad_unlink (prevpad, mypad);
 	gst_object_unref (mypad);
 
-	ghostpad = gst_element_get_static_pad (bin, "src");
-	nextpad = gst_element_get_static_pad (mp->priv->filterbin, "src");
+	ghostpad = gst_element_get_pad (bin, "src");
+	nextpad = gst_element_get_pad (mp->priv->filterbin, "src");
 
 	targetpad = gst_ghost_pad_get_target (GST_GHOST_PAD (nextpad));
 	if (targetpad == ghostpad) {
@@ -1293,7 +1210,7 @@ rb_player_gst_remove_filter (RBPlayerGstFilter *player, GstElement *element)
 		gst_ghost_pad_set_target (GST_GHOST_PAD (nextpad), prevpad);
 	} else {
 		/* we are in the middle, so link the previous and next elements */
-		mypad = gst_element_get_static_pad (bin, "src");
+		mypad = gst_element_get_pad (bin, "src");
 		gst_object_unref (nextpad);
 		nextpad = gst_pad_get_peer (mypad);
 		gst_pad_unlink (mypad, nextpad);
@@ -1317,37 +1234,182 @@ rb_player_gst_remove_filter (RBPlayerGstFilter *player, GstElement *element)
 	return result;
 }
 
-/*static gboolean
-rb_player_gst_add_data_tee (RBPlayerGstDataTee *player, GstElement *element)
+static void
+rb_player_gst_filter_init (RBPlayerGstFilterIface *iface)
 {
+	iface->add_filter = impl_add_filter;
+	iface->remove_filter = impl_remove_filter;
+}
 
+static void
+rb_player_gst_tee_init (RBPlayerGstTeeIface *iface)
+{
+	iface->add_tee = impl_add_tee;
+	iface->remove_tee = impl_remove_tee;
 }
 
-static gboolean
-rb_player_gst_remove_data_tee (RBPlayerGstDataTee *player, GstElement *element)
+
+
+RBPlayer *
+rb_player_gst_new (GError **error)
 {
+	return RB_PLAYER (g_object_new (RB_TYPE_PLAYER_GST, NULL, NULL));
+}
 
-}*/
 
 static void
-rb_player_gst_filter_init (RBPlayerGstFilterIface *iface)
+rb_player_gst_init (RBPlayerGst *mp)
 {
-	iface->add_filter = rb_player_gst_add_filter;
-	iface->remove_filter = rb_player_gst_remove_filter;
+	mp->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((mp),
+		    RB_TYPE_PLAYER_GST,
+		    RBPlayerGstPrivate));
 }
 
 static void
-rb_player_gst_tee_init (RBPlayerGstTeeIface *iface)
+impl_get_property (GObject *object,
+		   guint prop_id,
+		   GValue *value,
+		   GParamSpec *pspec)
+{
+	RBPlayerGst *mp = RB_PLAYER_GST (object);
+
+	switch (prop_id) {
+	case PROP_PLAYBIN:
+		g_value_set_object (value, mp->priv->playbin);
+		break;
+	case PROP_BUS:
+		if (mp->priv->playbin) {
+			GstBus *bus;
+			bus = gst_element_get_bus (mp->priv->playbin);
+			g_value_set_object (value, bus);
+			gst_object_unref (bus);
+		}
+		break;
+	case PROP_BUFFER_SIZE:
+		g_value_set_uint (value, mp->priv->buffer_size);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_set_property (GObject *object,
+		   guint prop_id,
+		   const GValue *value,
+		   GParamSpec *pspec)
+{
+	RBPlayerGst *mp = RB_PLAYER_GST (object);
+
+	switch (prop_id) {
+	case PROP_BUFFER_SIZE:
+		mp->priv->buffer_size = g_value_get_uint (value);
+		if (mp->priv->playbin != NULL) {
+			rb_debug ("setting buffer size on playbin: %d", mp->priv->buffer_size * 1024);
+			g_object_set (mp->priv->playbin, "buffer-size", mp->priv->buffer_size * 1024, NULL);
+		}
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBPlayerGst *mp;
+
+	mp = RB_PLAYER_GST (object);
+
+	if (mp->priv->tick_timeout_id != 0) {
+		g_source_remove (mp->priv->tick_timeout_id);
+		mp->priv->tick_timeout_id = 0;
+	}
+
+	if (mp->priv->playbin != NULL) {
+		gst_element_set_state (mp->priv->playbin, GST_STATE_NULL);
+		g_object_unref (mp->priv->playbin);
+		mp->priv->playbin = NULL;
+		mp->priv->audio_sink = NULL;
+	}
+
+	if (mp->priv->waiting_tees != NULL) {
+		g_list_foreach (mp->priv->waiting_tees, (GFunc)gst_object_sink, NULL);
+		g_list_free (mp->priv->waiting_tees);
+		mp->priv->waiting_tees = NULL;
+	}
+
+	if (mp->priv->waiting_filters != NULL) {
+		g_list_foreach (mp->priv->waiting_filters, (GFunc)gst_object_sink, NULL);
+		g_list_free (mp->priv->waiting_filters);
+		mp->priv->waiting_filters = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_player_gst_parent_class)->dispose (object);
+}
+
+static void
+rb_player_init (RBPlayerIface *iface)
 {
-	iface->add_tee = rb_player_gst_add_tee;
-	iface->remove_tee = rb_player_gst_remove_tee;
+	iface->open = impl_open;
+	iface->opened = impl_opened;
+	iface->close = impl_close;
+	iface->play = impl_play;
+	iface->pause = impl_pause;
+	iface->playing = impl_playing;
+	iface->set_volume = impl_set_volume;
+	iface->get_volume = impl_get_volume;
+	iface->set_replaygain = impl_set_replaygain;
+	iface->seekable = impl_seekable;
+	iface->set_time = impl_set_time;
+	iface->get_time = impl_get_time;
+	iface->multiple_open = (RBPlayerFeatureFunc) rb_false_function;
 }
 
-/*static void
-rb_player_gst_data_tee_init (RBPlayerGstDataTeeIface *iface)
+static void
+rb_player_gst_class_init (RBPlayerGstClass *klass)
 {
-	iface->add_data_tee = rb_player_gst_add_data_tee;
-	iface->remove_data_tee = rb_player_gst_remove_data_tee;
-}*/
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = impl_dispose;
+	object_class->get_property = impl_get_property;
+	object_class->set_property = impl_set_property;
 
+	g_object_class_install_property (object_class,
+					 PROP_PLAYBIN,
+					 g_param_spec_object ("playbin",
+							      "playbin",
+							      "playbin element",
+							      GST_TYPE_ELEMENT,
+							      G_PARAM_READABLE));
+	g_object_class_install_property (object_class,
+					 PROP_BUS,
+					 g_param_spec_object ("bus",
+							      "bus",
+							      "GStreamer message bus",
+							      GST_TYPE_BUS,
+							      G_PARAM_READABLE));
+	g_object_class_install_property (object_class,
+					 PROP_BUFFER_SIZE,
+					 g_param_spec_uint ("buffer-size",
+							    "buffer size",
+							    "Buffer size for network streams, in kB",
+							    64, MAX_NETWORK_BUFFER_SIZE, 128,
+							    G_PARAM_READWRITE));
+
+	signals[MISSING_PLUGINS] =
+		g_signal_new ("missing-plugins",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      0,	/* no point handling this internally */
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_POINTER_POINTER,
+			      G_TYPE_NONE,
+			      3,
+			      G_TYPE_POINTER, G_TYPE_STRV, G_TYPE_STRV);
+
+	g_type_class_add_private (klass, sizeof (RBPlayerGstPrivate));
+}
 
diff --git a/configure.ac b/configure.ac
index 1eb8f89..985ea34 100644
--- a/configure.ac
+++ b/configure.ac
@@ -29,7 +29,7 @@ AC_C_BIGENDIAN
 AC_CHECK_SIZEOF(long)
 
 DBUS_MIN_REQS=0.35
-GST_0_10_REQS=0.10.15
+GST_0_10_REQS=0.10.20
 GTK_REQS=2.12.0
 GLIB_REQS=2.16.0
 GNOME_MEDIA_PROFILES_REQS=2.8
diff --git a/plugins/visualizer/Makefile.am b/plugins/visualizer/Makefile.am
index eba7975..7c8c73b 100644
--- a/plugins/visualizer/Makefile.am
+++ b/plugins/visualizer/Makefile.am
@@ -6,8 +6,7 @@ plugin_LTLIBRARIES = libvisualizer.la
 libvisualizer_la_SOURCES = \
 	rb-visualizer-plugin.c				\
 	rb-vis-widget.c					\
-	rb-vis-widget.h					\
-	rb-fake-visualizer.c
+	rb-vis-widget.h
 
 libvisualizer_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
 
diff --git a/plugins/visualizer/rb-fake-visualizer.c b/plugins/visualizer/rb-fake-visualizer.c
deleted file mode 100644
index 2b81232..0000000
--- a/plugins/visualizer/rb-fake-visualizer.c
+++ /dev/null
@@ -1,609 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
- *
- *  Copyright (C) 2004 Benjamin Otte <otte gnome org>
- *  Copyright (C) 2006 Jonathan Matthew
- *
- *  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.
- *
- *  The Rhythmbox authors hereby grant permission for non-GPL compatible
- *  GStreamer plugins to be used and distributed together with GStreamer
- *  and Rhythmbox. This permission is above and beyond the permissions granted
- *  by the GPL license by which Rhythmbox is covered. If you modify this code
- *  you may extend this exception to your version of the code, but you are not
- *  obligated to do so. If you do not wish to do so, delete this exception
- *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
- */
-
-/* fake visualizer element for evil rhythmbox purposes */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <string.h>
-
-#include <gst/gst.h>
-#include <gst/base/gstadapter.h>
-#include <gst/video/video.h>
-#include <gst/audio/audio.h>
-
-#define RB_TYPE_FAKE_VIS (rb_fake_vis_get_type())
-#define RB_IS_FAKE_VIS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),RB_TYPE_FAKE_VIS))
-#define RB_FAKE_VIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),RB_TYPE_FAKE_VIS,RBFakeVis))
-#define RB_IS_FAKE_VIS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),RB_TYPE_FAKE_VIS))
-#define RB_FAKE_VIS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),RB_TYPE_FAKE_VIS,RBFakeVisClass))
-#define RB_FAKE_VIS_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), RB_TYPE_FAKE_VIS, RBFakeVisClass))
-
-typedef struct _RBFakeVis RBFakeVis;
-typedef struct _RBFakeVisClass RBFakeVisClass;
-
-GST_DEBUG_CATEGORY_STATIC (rb_fake_vis_debug);
-#define GST_CAT_DEFAULT (rb_fake_vis_debug)
-
-struct _RBFakeVis
-{
-  GstElement element;
-
-  /* pads */
-  GstPad *sinkpad;
-  GstPad *srcpad;
-  GstClockTime next_ts;
-  GstSegment segment;
-
-  /* audio/video state */
-  gint channels;
-  gint rate;                    /* Input samplerate */
-  gint bps;
-
-  /* framerate numerator & denominator */
-  gint fps_n;
-  gint fps_d;
-  gint width;
-  gint height;
-  gint depth;
-  GstClockTime duration;
-
-  /* samples per frame based on caps */
-  guint spf;
-
-  /* state stuff */
-  /*GstAdapter *adapter;*/
-  guint avail;
-  gboolean first_frame;
-
-  /* QoS stuff *//* with LOCK */
-  gdouble proportion;
-  GstClockTime earliest_time;
-};
-
-struct _RBFakeVisClass
-{
-  GstElementClass parent_class;
-};
-
-GType rb_fake_vis_get_type (void);
-
-
-static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
-    GST_PAD_SRC,
-    GST_PAD_ALWAYS,
-    GST_STATIC_CAPS (GST_VIDEO_CAPS_xRGB_HOST_ENDIAN)
-    );
-
-static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
-    GST_PAD_SINK,
-    GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("audio/x-raw-int, "
-        "width = (int) 16, "
-        "depth = (int) 16, "
-        "endianness = (int) BYTE_ORDER, "
-        "signed = (boolean) TRUE, "
-        "channels = (int) { 1, 2 }, " "rate = (int) [ 1000, MAX ]")
-    );
-
-static GstStateChangeReturn rb_fake_vis_change_state (GstElement * element,
-    GstStateChange transition);
-static GstFlowReturn rb_fake_vis_chain (GstPad * pad, GstBuffer * buffer);
-static gboolean rb_fake_vis_sink_event (GstPad * pad, GstEvent * event);
-static gboolean rb_fake_vis_src_event (GstPad * pad, GstEvent * event);
-
-static gboolean rb_fake_vis_sink_setcaps (GstPad * pad, GstCaps * caps);
-static gboolean rb_fake_vis_src_setcaps (GstPad * pad, GstCaps * caps);
-static GstCaps *rb_fake_vis_getcaps (GstPad * pad);
-
-static GstElementDetails rb_fake_vis_details =
-	GST_ELEMENT_DETAILS ("RB fake visualizer",
-			     "Visualization",
-			     "pretend to generate visualization from audio input",
-			     "Benjamin Otte <otte gnome org>, Jonathan Matthew <jonathan kaolin wh9 net>");
-
-static void
-_do_init (GType fake_vis_type)
-{
-  GST_DEBUG_CATEGORY_INIT (rb_fake_vis_debug,
-			   "fakevis", GST_DEBUG_FG_WHITE,
-			   "Rhythmbox built-in fake visualizer");
-}
-
-GST_BOILERPLATE_FULL (RBFakeVis, rb_fake_vis, GstElement, GST_TYPE_ELEMENT, _do_init);
-
-static void
-rb_fake_vis_base_init (gpointer g_class)
-{
-  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
-  gst_element_class_set_details (element_class, &rb_fake_vis_details);
-  gst_element_class_add_pad_template (element_class,
-				      gst_static_pad_template_get (&src_template));
-  gst_element_class_add_pad_template (element_class,
-				      gst_static_pad_template_get (&sink_template));
-}
-
-static void
-rb_fake_vis_class_init (RBFakeVisClass *klass)
-{
-  GstElementClass *element = GST_ELEMENT_CLASS (klass);
-  element->change_state = rb_fake_vis_change_state;
-}
-
-static void
-rb_fake_vis_init (RBFakeVis * visual, RBFakeVisClass *klass)
-{
-  /* create the sink and src pads */
-  visual->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
-  gst_pad_set_setcaps_function (visual->sinkpad, rb_fake_vis_sink_setcaps);
-  gst_pad_set_chain_function (visual->sinkpad, rb_fake_vis_chain);
-  gst_pad_set_event_function (visual->sinkpad, rb_fake_vis_sink_event);
-  gst_element_add_pad (GST_ELEMENT (visual), visual->sinkpad);
-
-  visual->srcpad = gst_pad_new_from_static_template (&src_template, "src");
-  gst_pad_set_setcaps_function (visual->srcpad, rb_fake_vis_src_setcaps);
-  gst_pad_set_getcaps_function (visual->srcpad, rb_fake_vis_getcaps);
-  gst_pad_set_event_function (visual->srcpad, rb_fake_vis_src_event);
-  gst_element_add_pad (GST_ELEMENT (visual), visual->srcpad);
-}
-
-static void
-rb_fake_vis_reset (RBFakeVis * visual)
-{
-  visual->next_ts = -1;
-  visual->avail = 0;
-  gst_segment_init (&visual->segment, GST_FORMAT_UNDEFINED);
-
-  GST_OBJECT_LOCK (visual);
-  visual->proportion = 1.0;
-  visual->earliest_time = -1;
-  visual->first_frame = FALSE;
-  GST_OBJECT_UNLOCK (visual);
-}
-
-static gboolean
-rb_fake_vis_src_setcaps (GstPad * pad, GstCaps * caps)
-{
-  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
-  GstStructure *structure;
-
-  structure = gst_caps_get_structure (caps, 0);
-
-  GST_DEBUG_OBJECT (visual, "src pad got caps %" GST_PTR_FORMAT, caps);
-
-  if (!gst_structure_get_int (structure, "width", &visual->width))
-    goto error;
-  if (!gst_structure_get_int (structure, "height", &visual->height))
-    goto error;
-  if (!gst_structure_get_int (structure, "bpp", &visual->depth))
-    goto error;
-  if (!gst_structure_get_fraction (structure, "framerate", &visual->fps_n,
-          &visual->fps_d))
-    goto error;
-
-  visual->spf =
-      gst_util_uint64_scale_int (visual->rate, visual->fps_d, visual->fps_n);
-  visual->duration =
-      gst_util_uint64_scale_int (GST_SECOND, visual->fps_d, visual->fps_n);
-
-  gst_object_unref (visual);
-  return TRUE;
-
-  /* ERRORS */
-error:
-  {
-    GST_DEBUG_OBJECT (visual, "error parsing caps");
-    gst_object_unref (visual);
-    return FALSE;
-  }
-}
-
-
-static gboolean
-rb_fake_vis_sink_setcaps (GstPad * pad, GstCaps * caps)
-{
-  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
-  GstStructure *structure;
-
-  structure = gst_caps_get_structure (caps, 0);
-
-  gst_structure_get_int (structure, "channels", &visual->channels);
-  gst_structure_get_int (structure, "rate", &visual->rate);
-
-  if (visual->fps_n != 0) {
-    visual->spf =
-        gst_util_uint64_scale_int (visual->rate, visual->fps_d, visual->fps_n);
-  }
-  visual->bps = visual->channels * sizeof (gint16);
-
-  gst_object_unref (visual);
-  return TRUE;
-}
-
-
-static GstCaps *
-rb_fake_vis_getcaps (GstPad * pad)
-{
-  GstCaps *ret;
-  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
-
-  ret = gst_caps_copy (gst_pad_get_pad_template_caps (visual->srcpad));
-
-  GST_DEBUG_OBJECT (visual, "returning caps %" GST_PTR_FORMAT, ret);
-  gst_object_unref (visual);
-  return ret;
-}
-
-
-static gboolean
-rb_fake_vis_src_negotiate (RBFakeVis * visual)
-{
-  GstCaps *othercaps, *target, *intersect;
-  GstStructure *structure;
-  const GstCaps *templ;
-
-  templ = gst_pad_get_pad_template_caps (visual->srcpad);
-
-  /* see what the peer can do */
-  othercaps = gst_pad_peer_get_caps (visual->srcpad);
-  if (othercaps) {
-    intersect = gst_caps_intersect (othercaps, templ);
-    gst_caps_unref (othercaps);
-
-    if (gst_caps_is_empty (intersect))
-      goto no_format;
-
-    target = gst_caps_copy_nth (intersect, 0);
-    gst_caps_unref (intersect);
-  } else {
-    target = gst_caps_ref ((GstCaps *) templ);
-  }
-
-  structure = gst_caps_get_structure (target, 0);
-  gst_structure_fixate_field_nearest_int (structure, "width", 1);
-  gst_structure_fixate_field_nearest_int (structure, "height", 1);
-  gst_structure_fixate_field_nearest_fraction (structure, "framerate", 1, 1);
-
-  gst_pad_set_caps (visual->srcpad, target);
-  gst_caps_unref (target);
-
-  return TRUE;
-
-  /* ERRORS */
-no_format:
-  {
-    GST_ELEMENT_ERROR (visual, STREAM, FORMAT, (NULL),
-        ("could not negotiate output format"));
-    gst_caps_unref (intersect);
-    return FALSE;
-  }
-}
-
-static gboolean
-rb_fake_vis_sink_event (GstPad * pad, GstEvent * event)
-{
-  RBFakeVis *visual;
-  gboolean res;
-
-  visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
-
-  switch (GST_EVENT_TYPE (event)) {
-    case GST_EVENT_FLUSH_START:
-      res = gst_pad_push_event (visual->srcpad, event);
-      break;
-    case GST_EVENT_FLUSH_STOP:
-      rb_fake_vis_reset (visual);
-      res = gst_pad_push_event (visual->srcpad, event);
-      break;
-    case GST_EVENT_NEWSEGMENT:
-    {
-      GstFormat format;
-      gdouble rate, arate;
-      gint64 start, stop, time;
-      gboolean update;
-
-      /* the newsegment values are used to clip the input samples
-       * and to convert the incoming timestamps to running time so
-       * we can do QoS */
-      gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
-          &start, &stop, &time);
-
-      /* now configure the values */
-      gst_segment_set_newsegment_full (&visual->segment, update,
-          rate, arate, format, start, stop, time);
-
-      /* and forward */
-      res = gst_pad_push_event (visual->srcpad, event);
-      break;
-    }
-    default:
-      res = gst_pad_push_event (visual->srcpad, event);
-      break;
-  }
-
-  gst_object_unref (visual);
-  return res;
-}
-
-static gboolean
-rb_fake_vis_src_event (GstPad * pad, GstEvent * event)
-{
-  RBFakeVis *visual;
-  gboolean res;
-
-  visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
-
-  switch (GST_EVENT_TYPE (event)) {
-    case GST_EVENT_QOS:
-    {
-      gdouble proportion;
-      GstClockTimeDiff diff;
-      GstClockTime timestamp;
-
-      gst_event_parse_qos (event, &proportion, &diff, &timestamp);
-
-      /* save stuff for the _chain function */
-      GST_OBJECT_LOCK (visual);
-      visual->proportion = proportion;
-      if (diff >= 0)
-        /* we're late, this is a good estimate for next displayable
-         * frame (see part-qos.txt) */
-        visual->earliest_time = timestamp + 2 * diff + visual->duration;
-      else
-        visual->earliest_time = timestamp + diff;
-
-      GST_OBJECT_UNLOCK (visual);
-
-      res = gst_pad_push_event (visual->sinkpad, event);
-      break;
-    }
-    default:
-      res = gst_pad_push_event (visual->sinkpad, event);
-      break;
-  }
-
-  gst_object_unref (visual);
-  return res;
-}
-
-/* allocate and output buffer, if no format was negotiated, this
- * function will negotiate one. After calling this function, a
- * reverse negotiation could have happened. */
-static GstFlowReturn
-get_buffer (RBFakeVis * visual, GstBuffer ** outbuf)
-{
-  GstFlowReturn ret;
-  guint outsize;
-
-  /* we don't know an output format yet, pick one */
-  if (GST_PAD_CAPS (visual->srcpad) == NULL) {
-    if (!rb_fake_vis_src_negotiate (visual))
-      return GST_FLOW_NOT_NEGOTIATED;
-  }
-
-  outsize = visual->height * visual->width * (visual->depth / 8);
-
-  GST_DEBUG_OBJECT (visual, "allocating output buffer with caps %"
-      GST_PTR_FORMAT, GST_PAD_CAPS (visual->srcpad));
-
-  /* now allocate a buffer with the last negotiated format.
-   * Downstream could renegotiate a new format, which will trigger
-   * our setcaps function on the source pad. */
-  ret =
-      gst_pad_alloc_buffer_and_set_caps (visual->srcpad,
-      GST_BUFFER_OFFSET_NONE, outsize,
-      GST_PAD_CAPS (visual->srcpad), outbuf);
-
-  /* no buffer allocated, we don't care why. */
-  if (ret != GST_FLOW_OK)
-    return ret;
-
-  /* this is bad and should not happen. When the alloc function
-   * returns _OK, core ensures we have a valid buffer. */
-  if (*outbuf == NULL)
-    return GST_FLOW_ERROR;
-
-  memset (GST_BUFFER_DATA (*outbuf), 0, outsize);
-
-  return GST_FLOW_OK;
-}
-
-static GstFlowReturn
-rb_fake_vis_chain (GstPad * pad, GstBuffer * buffer)
-{
-  GstBuffer *outbuf = NULL;
-  RBFakeVis *visual = RB_FAKE_VIS (gst_pad_get_parent (pad));
-  GstFlowReturn ret = GST_FLOW_OK;
-
-  GST_DEBUG_OBJECT (visual, "chain function called");
-
-  /* If we don't have an output format yet, preallocate a buffer to try and
-   * set one */
-  if (GST_PAD_CAPS (visual->srcpad) == NULL) {
-    GST_DEBUG_OBJECT (visual, "calling buffer alloc to set caps");
-    ret = get_buffer (visual, &outbuf);
-    if (ret != GST_FLOW_OK) {
-      GST_DEBUG_OBJECT (visual, "couldn't allocate buffer: %s", gst_flow_get_name (ret));
-      gst_buffer_unref (buffer);
-      goto beach;
-    }
-  }
-
-  /* resync on DISCONT */
-  if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) {
-    visual->avail = 0;
-    visual->next_ts = -1;
-  }
-
-  /* Try to push a frame as soon as possible to avoid stalling the pipeline */
-  if (visual->first_frame == FALSE) {
-    if (outbuf == NULL) {
-      ret = get_buffer (visual, &outbuf);
-      if (ret != GST_FLOW_OK) {
-	goto beach;
-      }
-    }
-    GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buffer);
-    ret = gst_pad_push (visual->srcpad, outbuf);
-    if (ret != GST_FLOW_OK) {
-      goto beach;
-    }
-    outbuf = NULL;
-    visual->first_frame = TRUE;
-  }
-
-  /* Match timestamps from the incoming audio */
-  if (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE)
-    visual->next_ts = GST_BUFFER_TIMESTAMP (buffer);
-
-  GST_DEBUG_OBJECT (visual,
-      "Input buffer has %d samples, time=%" G_GUINT64_FORMAT,
-      GST_BUFFER_SIZE (buffer) / visual->bps, GST_BUFFER_TIMESTAMP (buffer));
-
-  visual->avail += GST_BUFFER_SIZE (buffer);
-  gst_buffer_unref (buffer);
-
-  while (visual->avail > MAX (512, visual->spf) * visual->bps) {
-    gboolean need_skip;
-
-    GST_DEBUG_OBJECT (visual, "processing buffer (%u avail)", visual->avail);
-
-    if (visual->next_ts != -1) {
-      gint64 qostime;
-
-      /* QoS is done on running time */
-      qostime = gst_segment_to_running_time (&visual->segment, GST_FORMAT_TIME,
-          visual->next_ts);
-
-      GST_OBJECT_LOCK (visual);
-      /* check for QoS, don't compute buffers that are known to be late */
-      need_skip = visual->earliest_time != -1 &&
-          qostime <= visual->earliest_time;
-      GST_OBJECT_UNLOCK (visual);
-
-      if (need_skip) {
-        GST_WARNING_OBJECT (visual,
-            "QoS: skip ts: %" GST_TIME_FORMAT ", earliest: %" GST_TIME_FORMAT,
-            GST_TIME_ARGS (qostime), GST_TIME_ARGS (visual->earliest_time));
-        goto skip;
-      }
-    }
-    /* alloc a buffer if we don't have one yet, this happens
-     * when we pushed a buffer in this while loop before */
-    if (outbuf == NULL) {
-      ret = get_buffer (visual, &outbuf);
-      if (ret != GST_FLOW_OK) {
-        goto beach;
-      }
-    }
-
-    GST_BUFFER_TIMESTAMP (outbuf) = visual->next_ts;
-    GST_BUFFER_DURATION (outbuf) = visual->duration;
-
-    ret = gst_pad_push (visual->srcpad, outbuf);
-    outbuf = NULL;
-
-    GST_DEBUG_OBJECT (visual, "finished frame, flushing %u samples from input",
-        visual->spf);
-  skip:
-    /* interpolate next timestamp */
-    if (visual->next_ts != -1)
-      visual->next_ts += visual->duration;
-
-    /* Flush out the number of samples per frame * channels * sizeof (gint16) */
-    if (visual->avail < visual->spf * visual->bps)
-      visual->avail = 0;
-    else
-      visual->avail -= visual->spf * visual->bps;
-
-    /* quit the loop if something was wrong */
-    if (ret != GST_FLOW_OK)
-      break;
-  }
-
-  if (outbuf != NULL)
-    gst_buffer_unref (outbuf);
-
-beach:
-  gst_object_unref (visual);
-
-  GST_DEBUG_OBJECT (visual, "leaving chain function");
-  return ret;
-}
-
-static GstStateChangeReturn
-rb_fake_vis_change_state (GstElement * element, GstStateChange transition)
-{
-  RBFakeVis *visual = RB_FAKE_VIS (element);
-  GstStateChangeReturn ret;
-
-  switch (transition) {
-    case GST_STATE_CHANGE_NULL_TO_READY:
-      break;
-    case GST_STATE_CHANGE_READY_TO_PAUSED:
-      rb_fake_vis_reset (visual);
-      break;
-    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
-      break;
-    default:
-      break;
-  }
-
-  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
-
-  switch (transition) {
-    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
-      break;
-    case GST_STATE_CHANGE_PAUSED_TO_READY:
-      break;
-    case GST_STATE_CHANGE_READY_TO_NULL:
-      break;
-    default:
-      break;
-  }
-
-  return ret;
-}
-
-static gboolean
-plugin_init (GstPlugin * plugin)
-{
-  return gst_element_register (plugin, "rbfakevis", GST_RANK_NONE, RB_TYPE_FAKE_VIS);
-}
-
-GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR, GST_VERSION_MINOR,
-			  "rbfakevis",
-			  "fake visualizer",
-			  plugin_init,
-			  VERSION,
-			  "GPL",
-			  PACKAGE,
-			  "");
-
diff --git a/plugins/visualizer/rb-visualizer-plugin.c b/plugins/visualizer/rb-visualizer-plugin.c
index 5982f47..53d1b74 100644
--- a/plugins/visualizer/rb-visualizer-plugin.c
+++ b/plugins/visualizer/rb-visualizer-plugin.c
@@ -82,9 +82,6 @@
 
 #include "rb-vis-widget.h"
 
-/* not going to create a new header just for this */
-extern GType rb_fake_vis_get_type (void);
-
 /* preferences */
 #define CONF_VIS_PREFIX  CONF_PREFIX "/plugins/visualizer"
 #define CONF_VIS_ELEMENT CONF_VIS_PREFIX "/element"
@@ -108,6 +105,8 @@ extern GType rb_fake_vis_get_type (void);
 
 #define VISUALIZER_DBUS_PATH	"/org/gnome/Rhythmbox/Visualizer"
 
+/* playbin2 flag(s) */
+#define PLAYBIN2_FLAG_VIS	0x08
 
 typedef struct {
 	char *name;
@@ -240,13 +239,6 @@ static const VisualizerQuality vis_quality[] = {
 	{ N_("Extra Large"),	800,	600,	30,	1 },
 };
 
-/* "quality" to use for fake visualization; should be small and
- * low framerate, but not so slow it makes a noticeable difference
- * to playback start time.  the video sink needs to preroll too,
- * so if the framerate is 1/10, that will take 10 seconds.
- */
-static const VisualizerQuality fake_vis_quality = { "", 60, 60, 1, 1 };
-
 static const VisualizerModeName vis_mode_name[] = {
 	{ N_("Embedded"),	EMBEDDED },
 	{ N_("Fullscreen"),	FULLSCREEN },
@@ -443,12 +435,7 @@ bus_sync_message_cb (GstBus *bus, GstMessage *msg, RBVisualizerPlugin *plugin)
 	case EXTERNAL_WINDOW:
 		if (plugin->vis_widget != NULL) {
 			g_object_get (plugin->vis_widget, "window-xid", &window, NULL);
-			if (window == 0) {
-				window = GDK_WINDOW_XWINDOW (plugin->fake_window);
-				rb_debug ("setting fake window id %lu", window);
-			} else {
-				rb_debug ("setting window id %lu in prepare-xwindow-id handler", window);
-			}
+			rb_debug ("setting window id %lu in prepare-xwindow-id handler", window);
 		}
 		break;
 	case DESKTOP_WINDOW:
@@ -457,7 +444,7 @@ bus_sync_message_cb (GstBus *bus, GstMessage *msg, RBVisualizerPlugin *plugin)
 		break;
 	}
 
-	if (plugin->xoverlay != NULL)
+	if (plugin->xoverlay != NULL && window != 0)
 		gst_x_overlay_set_xwindow_id (plugin->xoverlay, window);
 	plugin->window_id_set = TRUE;
 }
@@ -477,10 +464,7 @@ fixate_vis_caps (RBVisualizerPlugin *pi, GstElement *vis_element, GstElement *ca
 	if (quality < 0 || quality > G_N_ELEMENTS (vis_quality))
 		quality = DEFAULT_VIS_QUALITY;
 
-	if (pi->active)
-		q = &vis_quality[quality];
-	else
-		q = &fake_vis_quality;
+	q = &vis_quality[quality];
 
 	pad = gst_element_get_static_pad (vis_element, "src");
 	template_caps = gst_pad_get_pad_template_caps (pad);
@@ -554,12 +538,24 @@ update_playbin_visualizer (RBVisualizerPlugin *plugin,
 {
 	GstPad *pad;
 	GstElement *vis_plugin;
+	int playbin_flags;
 
 	if (plugin->playbin == NULL)
 		return;
 
-	if (plugin->visualizer)
+	if (plugin->visualizer) {
 		g_object_unref (plugin->visualizer);
+		plugin->visualizer = NULL;
+	}
+
+	g_object_get (plugin->playbin, "flags", &playbin_flags, NULL);
+
+	if (plugin->active == FALSE) {
+		playbin_flags &= ~PLAYBIN2_FLAG_VIS;
+		rb_debug ("disabling vis; new playbin2 flags %d", playbin_flags);
+		g_object_set (plugin->playbin, "flags", playbin_flags, NULL);
+		return;
+	}
 
 	plugin->visualizer = gst_bin_new (NULL);
 
@@ -572,13 +568,8 @@ update_playbin_visualizer (RBVisualizerPlugin *plugin,
 	gst_object_unref (pad);
 
 	/* set up visualizer */
-	if (plugin->active) {
-		vis_plugin = create_visualizer_element (vis_override);
-		gst_bin_add (GST_BIN (plugin->visualizer), vis_plugin);
-	} else {
-		vis_plugin = g_object_new (rb_fake_vis_get_type (), NULL);
-		gst_bin_add (GST_BIN (plugin->visualizer), vis_plugin);
-	}
+	vis_plugin = create_visualizer_element (vis_override);
+	gst_bin_add (GST_BIN (plugin->visualizer), vis_plugin);
 
 	pad = gst_element_get_static_pad (vis_plugin, "sink");
 	gst_element_add_pad (plugin->visualizer, gst_ghost_pad_new ("sink", pad));
@@ -589,7 +580,12 @@ update_playbin_visualizer (RBVisualizerPlugin *plugin,
 
 	g_object_ref (plugin->visualizer);
 
-	g_object_set (plugin->playbin, "vis-plugin", plugin->visualizer, NULL);
+	playbin_flags |= PLAYBIN2_FLAG_VIS;
+	rb_debug ("enabling vis; new playbin2 flags %d", playbin_flags);
+	g_object_set (plugin->playbin,
+		      "vis-plugin", plugin->visualizer,
+		      "flags", playbin_flags,
+		      NULL);
 }
 
 static void
@@ -1618,8 +1614,6 @@ impl_activate (RBPlugin *plugin,
 	GtkAction *action;
 	char *ui_file;
 
-	rb_fake_vis_get_type ();
-
 	pi->shell = shell;
 
 	/* find the player backend and connect to its pipeline mutation signal */
@@ -1913,10 +1907,6 @@ vis_plugin_filter (GstPluginFeature *feature, gpointer data)
 {
 	GstElementFactory *f;
 
-	/* skip our fake visualizer */
-	if (strcmp (gst_plugin_feature_get_name (feature), "rbfakevis") == 0)
-		return FALSE;
-
 	if  (!GST_IS_ELEMENT_FACTORY (feature))
 		return FALSE;
 	f = GST_ELEMENT_FACTORY (feature);
diff --git a/shell/rb-shell-preferences.c b/shell/rb-shell-preferences.c
index e0302b4..372302c 100644
--- a/shell/rb-shell-preferences.c
+++ b/shell/rb-shell-preferences.c
@@ -666,7 +666,6 @@ update_playback_prefs_sensitivity (RBShellPreferences *preferences)
 	backend = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (preferences->priv->xfade_backend_check));
 
 	gtk_widget_set_sensitive (preferences->priv->album_crossfade_check, backend);
-	gtk_widget_set_sensitive (preferences->priv->network_buffer_size, backend);
 	gtk_widget_set_sensitive (preferences->priv->transition_duration, backend);
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]