diff --git a/configure.ac b/configure.ac
index fdd49f4..356220d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -153,6 +153,21 @@ BANSHEE_CHECK_TORRENT
 dnl Moblin integration (optional)
 BANSHEE_CHECK_MOBLIN
 
+dnl Gapless, if we have a new-enough playbin2
+ENABLE_GAPLESS="no"
+AC_ARG_ENABLE(gapless-playback,
+	AC_HELP_STRING([--enable-gapless-playback],
+		[Enable gapless playback engine (requires gst-plugins-base > 0.10.25.2)]),
+		enable_gapless=$enableval,
+		enable_gapless=auto)
+if test "x$enable_gapless" != "xno" ; then
+   	PKG_CHECK_MODULES([GST_PLUGINS_BASE], [gstreamer-plugins-base-0.10 > 0.10.25.2], ENABLE_GAPLESS=yes, ENABLE_GAPLESS=no)
+   	if test "x$enable_gapless" == "xyes" -a "x$ENABLE_GAPLESS" == "xno" ; then
+	   	AC_MSG_ERROR([Gapless playback requires gstreamer-plugins-base > 0.10.25.2])
+	fi
+fi
+AM_CONDITIONAL(ENABLE_GAPLESS, test "x$ENABLE_GAPLESS" = "xyes")   
+
 dnl i18n
 SHAMROCK_CONFIGURE_I18N($PACKAGE)
 
@@ -347,6 +362,7 @@ ${PACKAGE}-${VERSION}
     Library Watcher:   ${HAVE_MONO_2_4_3} (requires Mono >= 2.4.3)
     Podcasts:          ${enable_podcast}
     Wikipedia:         ${enable_webkit} (requires webkit-sharp)
+    Gapless playback:  ${ENABLE_GAPLESS} (requires gstreamer-plugins-base > 0.10.25.2)
 
   Build/Development:
     Unit Tests:        ${do_tests} (requires nunit >= ${NUNIT_REQUIRED})
diff --git a/libbanshee/Makefile.am b/libbanshee/Makefile.am
index a35eeb3..415b329 100644
--- a/libbanshee/Makefile.am
+++ b/libbanshee/Makefile.am
@@ -6,6 +6,10 @@ INCLUDES = \
 	$(LIBBANSHEE_CFLAGS) \
 	$(GST_CFLAGS) 
 
+if ENABLE_GAPLESS
+INCLUDES += -DENABLE_GAPLESS
+endif
+
 bansheelibdir = $(pkglibdir)
 bansheelib_LTLIBRARIES = libbanshee.la
 
diff --git a/libbanshee/banshee-player-pipeline.c b/libbanshee/banshee-player-pipeline.c
index d246c92..0993519 100644
--- a/libbanshee/banshee-player-pipeline.c
+++ b/libbanshee/banshee-player-pipeline.c
@@ -38,6 +38,15 @@
 // Private Functions
 // ---------------------------------------------------------------------------
 
+static gboolean
+bp_stream_has_video (GstElement *playbin)
+{
+    int n_video;
+    g_object_get (G_OBJECT (playbin), "n-video", &n_video, NULL);
+    return n_video > 0;
+}
+
+
 static void
 bp_pipeline_process_tag (const GstTagList *tag_list, const gchar *tag_name, BansheePlayer *player)
 {
@@ -53,13 +62,44 @@ bp_pipeline_process_tag (const GstTagList *tag_list, const gchar *tag_name, Bans
     
     value = gst_tag_list_get_value_index (tag_list, tag_name, 0);
 
-    if (value != NULL) {
-        _bp_replaygain_process_tag (player, tag_name, value);
+    if (value != NULL && player->tag_found_cb != NULL) {
+        player->tag_found_cb (player, tag_name, value);
+    }
+}
+
+static gboolean
+bp_next_track_starting (BansheePlayer *player)
+{
+    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
+    g_return_val_if_fail (GST_IS_ELEMENT (player->playbin), FALSE);
+
+    // Work around BGO #602437 - gapless transition between tracks with 
+    // video streams results in broken behaviour - most obviously, huge A/V
+    // sync issues.
+    gboolean has_video = bp_stream_has_video (player->playbin);
+    if (player->in_gapless_transition && has_video) {
+        gchar *uri;
     
-        if (player->tag_found_cb != NULL) {
-            player->tag_found_cb (player, tag_name, value);
-        }
+        bp_debug ("[Gapless]: Aborting gapless transition to stream with video.");
+        bp_debug ("[Gapless]: Triggering normal track change.");
+        g_object_get (G_OBJECT (player->playbin), "uri", &uri, NULL);
+        gst_element_set_state (player->playbin, GST_STATE_READY);
+        
+        g_object_set (G_OBJECT (player->playbin), "uri", uri, NULL);
+        gst_element_set_state (player->playbin, GST_STATE_PLAYING);
+        g_free (uri);
+        player->in_gapless_transition = FALSE;
+        // The transition to playing will happen asynchronously, and will trigger
+        // a second track-starting message.  Stop processing this one.
+        return FALSE;
+    }
+    player->in_gapless_transition = FALSE;
+
+    if (player->next_track_starting_cb != NULL) {
+        bp_debug ("[gapless] Triggering track-change signal");
+        player->next_track_starting_cb (player);
     }
+    return FALSE;
 }
 
 static gboolean
@@ -83,9 +123,8 @@ bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
             gst_message_parse_state_changed (message, &old, &new, &pending);
             
             _bp_missing_elements_handle_state_changed (player, old, new);
-            _bp_replaygain_handle_state_changed (player, old, new, pending);
             
-            if (player->state_changed_cb != NULL) {
+            if (player->state_changed_cb != NULL && GST_MESSAGE_SRC (message) == GST_OBJECT (player->playbin)) {
                 player->state_changed_cb (player, old, new, pending);
             }
             break;
@@ -161,6 +200,11 @@ bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
         } 
         
         case GST_MESSAGE_ELEMENT: {
+            const GstStructure *messageStruct;
+            messageStruct = gst_message_get_structure (message);
+            if (GST_MESSAGE_SRC (message) == GST_OBJECT (player->playbin) && gst_structure_has_name (messageStruct, "playbin2-stream-changed")) {
+                bp_next_track_starting (player);
+            }
             _bp_missing_elements_process_message (player, message);
             break;
         }
@@ -171,6 +215,26 @@ bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
     return TRUE;
 }
 
+#ifdef ENABLE_GAPLESS
+static void bp_about_to_finish_callback (GstElement *playbin, BansheePlayer *player)
+{
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+    g_return_if_fail (GST_IS_ELEMENT (playbin));
+
+    if (bp_stream_has_video (playbin)) {
+        bp_debug ("[Gapless]: Not attempting gapless transition from stream with video");
+        return;
+    }
+
+    if (player->about_to_finish_cb != NULL) {
+        player->in_gapless_transition = TRUE;
+
+        bp_debug ("[Gapless] Requesting next track");
+        player->about_to_finish_cb (player);
+    }
+}
+#endif //ENABLE_GAPLESS
+
 // ---------------------------------------------------------------------------
 // Internal Functions
 // ---------------------------------------------------------------------------
@@ -189,7 +253,14 @@ _bp_pipeline_construct (BansheePlayer *player)
     
     // Playbin is the core element that handles autoplugging (finding the right
     // source and decoder elements) based on source URI and stream content
-    player->playbin = gst_element_factory_make ("playbin", "playbin");
+    player->playbin = gst_element_factory_make ("playbin2", "playbin");
+
+#ifdef ENABLE_GAPLESS
+    // Connect a proxy about-to-finish callback that will generate a next-track-starting callback.
+    // This can be removed once playbin2 generates its own next-track signal.
+    g_signal_connect (player->playbin, "about-to-finish", G_CALLBACK (bp_about_to_finish_callback), player);
+#endif //ENABLE_GAPLESS
+
     g_return_val_if_fail (player->playbin != NULL, FALSE);
 
     // Try to find an audio sink, prefer gconf, which typically is set to auto these days,
@@ -256,11 +327,17 @@ _bp_pipeline_construct (BansheePlayer *player)
         // link in equalizer, preamp and audioconvert.
         gst_element_link_many (audiosinkqueue, eq_audioconvert, player->preamp, 
             player->equalizer, eq_audioconvert2, audiosink, NULL);
+        player->before_rgvolume = eq_audioconvert;
+        player->after_rgvolume = player->preamp;
     } else {
         // link the queue with the real audio sink
         gst_element_link (audiosinkqueue, audiosink);
+        player->before_rgvolume = audiosinkqueue;
+        player->after_rgvolume = audiosink;
     }
-    
+    player->rgvolume_in_pipeline = FALSE;
+    _bp_replaygain_pipeline_rebuild (player);
+
     _bp_vis_pipeline_setup (player);
     
     // Now that our internal audio sink is constructed, tell playbin to use it
diff --git a/libbanshee/banshee-player-pipeline.h b/libbanshee/banshee-player-pipeline.h
index 6a0b9aa..7f5c00f 100644
--- a/libbanshee/banshee-player-pipeline.h
+++ b/libbanshee/banshee-player-pipeline.h
@@ -33,5 +33,6 @@
 
 gboolean  _bp_pipeline_construct (BansheePlayer *player);
 void      _bp_pipeline_destroy   (BansheePlayer *player);
+void      _bp_pipeline_rebuild   (BansheePlayer* player);
 
 #endif /* _BANSHEE_PLAYER_PIPELINE_H */
diff --git a/libbanshee/banshee-player-private.h b/libbanshee/banshee-player-private.h
index 5085ca1..1edd375 100644
--- a/libbanshee/banshee-player-private.h
+++ b/libbanshee/banshee-player-private.h
@@ -76,6 +76,8 @@ typedef void (* BansheePlayerIterateCallback)      (BansheePlayer *player);
 typedef void (* BansheePlayerBufferingCallback)    (BansheePlayer *player, gint buffering_progress);
 typedef void (* BansheePlayerTagFoundCallback)     (BansheePlayer *player, const gchar *tag, const GValue *value);
 typedef void (* BansheePlayerVisDataCallback)      (BansheePlayer *player, gint channels, gint samples, gfloat *data, gint bands, gfloat *spectrum);
+typedef void (* BansheePlayerNextTrackStartingCallback)     (BansheePlayer *player);
+typedef void (* BansheePlayerAboutToFinishCallback)         (BansheePlayer *player);
 typedef GstElement * (* BansheePlayerVideoPipelineSetupCallback) (BansheePlayer *player, GstBus *bus);
 
 typedef enum {
@@ -93,6 +95,8 @@ struct BansheePlayer {
     BansheePlayerBufferingCallback buffering_cb;
     BansheePlayerTagFoundCallback tag_found_cb;
     BansheePlayerVisDataCallback vis_data_cb;
+    BansheePlayerNextTrackStartingCallback next_track_starting_cb;
+    BansheePlayerAboutToFinishCallback about_to_finish_cb;
     BansheePlayerVideoPipelineSetupCallback video_pipeline_setup_cb;
 
     // Pipeline Elements
@@ -101,6 +105,13 @@ struct BansheePlayer {
     GstElement *audiobin;
     GstElement *equalizer;
     GstElement *preamp;
+    GstElement *volume;
+    GstElement *rgvolume;
+
+    GstElement *before_rgvolume;
+    GstElement *after_rgvolume;
+    gboolean   rgvolume_in_pipeline;
+
     gint equalizer_status;
     gdouble current_volume;
     
@@ -110,6 +121,7 @@ struct BansheePlayer {
     guint iterate_timeout_id;
     gboolean buffering;
     gchar *cdda_device;
+    gboolean in_gapless_transition;
     
     // Video State
     BpVideoDisplayContextType video_display_context_type;
@@ -143,15 +155,8 @@ struct BansheePlayer {
     // and the oldest at index 10. History is used to compute 
     // gain on a track where no adjustment information is present.
     // http://replaygain.hydrogenaudio.org/player_scale.html
-    gdouble volume_scale_history[11];
-    gboolean volume_scale_history_shift;
-    gboolean current_scale_from_history;
-    
-    // ReplayGain cache
-    gdouble album_gain;
-    gdouble album_peak;
-    gdouble track_gain;
-    gdouble track_peak;
+    gdouble rg_gain_history[10];
+    gint history_size;
 };
 
 #endif /* _BANSHEE_PLAYER_PRIVATE_H */
diff --git a/libbanshee/banshee-player-replaygain.c b/libbanshee/banshee-player-replaygain.c
index 78fe083..22b171f 100644
--- a/libbanshee/banshee-player-replaygain.c
+++ b/libbanshee/banshee-player-replaygain.c
@@ -28,128 +28,164 @@
 
 #include <math.h>
 #include "banshee-player-replaygain.h"
+#include "banshee-player-pipeline.h"
 
 // ---------------------------------------------------------------------------
 // Private Functions
 // ---------------------------------------------------------------------------
 
-static inline void
-bp_replaygain_debug (BansheePlayer *player)
+static gdouble
+bp_replaygain_db_to_linear(gdouble value)
 {
-    gint i;
-    for (i = 0; i < 11; i++) {
-       printf ("%g ", player->volume_scale_history[i]);
+    return pow(10, value / 20.0);
+}
+
+static gdouble bp_rg_calc_history_avg (BansheePlayer *player)
+{
+    gdouble sum = 0.0;
+    int i;
+    for (i = 0; i < player->history_size; ++i) {
+        sum += player->rg_gain_history[i];
     }
-    printf ("\n");
+    return sum / player->history_size;
 }
 
-static void
-bp_replaygain_update_pipeline (BansheePlayer *player, 
-    gdouble album_gain, gdouble album_peak,
-    gdouble track_gain, gdouble track_peak)
+static void bp_replaygain_update_history (BansheePlayer *player)
 {
-    gdouble gain = album_gain == 0.0 ? album_gain : track_gain;
-    gdouble peak = album_peak == 0.0 ? album_peak : track_peak;
-    gdouble scale = 0.0;
-    
+    g_return_if_fail (player->history_size <= 10);
+
+    if (player->history_size == 10) {
+        memmove (player->rg_gain_history + 1, player->rg_gain_history, sizeof (gdouble) * 9);
+    } else {
+        memmove (player->rg_gain_history + 1, player->rg_gain_history, sizeof (gdouble) * player->history_size);
+        player->history_size++;
+    }
+
+    gdouble gain;
+    g_object_get (G_OBJECT (player->rgvolume), "target-gain", &gain, NULL);
+    player->rg_gain_history[0] = gain;
+    bp_debug ("[ReplayGain] Added gain: %.2f to history.", gain);
+
+    g_object_set (G_OBJECT (player->rgvolume), "fallback-gain", bp_rg_calc_history_avg (player), NULL);
+}
+
+static void on_target_gain_changed (GstElement *rgvolume, GParamSpec *pspec, BansheePlayer *player)
+{
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+
+    bp_replaygain_update_history (player);
+    _bp_rgvolume_print_volume (player);
+}
+
+static void
+pad_block_cb (GstPad *srcPad, gboolean blocked, gpointer user_data) {
+
+    if (!blocked) {
+        return;
+    }
+
+    BansheePlayer* player = (BansheePlayer*) user_data;
     g_return_if_fail (IS_BANSHEE_PLAYER (player));
-    
-    if (gain == 0.0) {
-        gint i;
-        player->current_scale_from_history = TRUE;
-        // Compute the average scale from history
-        for (i = 1; i <= 10; i++) {
-            scale += player->volume_scale_history[i] / 10.0;
+
+    // The pad_block_cb can get triggered multiple times, on different threads.
+    // Lock around the link/unlink code, so we don't end up going through here
+    // with inconsistent state.
+    g_mutex_lock (player->mutex);
+
+    if ((player->replaygain_enabled && player->rgvolume_in_pipeline) ||
+        (!player->replaygain_enabled && !player->rgvolume_in_pipeline)) {
+        // The pipeline is already in the correct state.  Unblock the pad, and return.
+        g_mutex_unlock (player->mutex);
+        if (gst_pad_is_blocked (srcPad)) {
+            gst_pad_set_blocked_async(srcPad, FALSE, &pad_block_cb, player);
         }
+        return;
+    }
+
+    if (player->rgvolume_in_pipeline) {
+        gst_element_unlink(player->before_rgvolume, player->rgvolume);
+        gst_element_unlink(player->rgvolume, player->after_rgvolume);
     } else {
-        player->current_scale_from_history = FALSE;
-        scale = pow (10.0, gain / 20.0);
-        
-        if (peak != 0.0 && scale * peak > 1.0) {
-            scale = 1.0 / peak;
-        }
-        
-        if (scale > 15.0) {
-            scale = 15.0;
+        gst_element_unlink(player->before_rgvolume, player->after_rgvolume);
+    }
+
+    if (player->replaygain_enabled) {
+        player->rgvolume = _bp_rgvolume_new (player);
+        if (!GST_IS_ELEMENT (player->rgvolume)) {
+            player->replaygain_enabled = FALSE;
         }
+    } else {
+        gst_element_set_state (player->rgvolume, GST_STATE_NULL);
+        gst_bin_remove (GST_BIN (player->audiobin), player->rgvolume);
     }
-    
-    player->volume_scale_history[0] = scale;
-    _bp_replaygain_update_volume (player);
+
+    if (player->replaygain_enabled && GST_IS_ELEMENT (player->rgvolume)) {
+        g_signal_connect (player->rgvolume, "notify::target-gain", G_CALLBACK (on_target_gain_changed), player);
+        gst_bin_add (GST_BIN (player->audiobin), player->rgvolume);
+        gst_element_sync_state_with_parent(player->rgvolume);
+
+        // link in rgvolume and connect to the real audio sink
+        gst_element_link (player->before_rgvolume, player->rgvolume);
+        gst_element_link (player->rgvolume, player->after_rgvolume);
+        player->rgvolume_in_pipeline = TRUE;
+    } else {
+        // link the queue with the real audio sink
+        gst_element_link (player->before_rgvolume, player->after_rgvolume);
+        player->rgvolume_in_pipeline = FALSE;
+    }
+
+    // Our state is now consistent
+    g_mutex_unlock (player->mutex);
+
+    if (gst_pad_is_blocked (srcPad)) {
+        gst_pad_set_blocked_async(srcPad, FALSE, &pad_block_cb, player);
+    }
+
+    _bp_rgvolume_print_volume(player);
 }
 
 // ---------------------------------------------------------------------------
 // Internal Functions
 // ---------------------------------------------------------------------------
 
-void
-_bp_replaygain_process_tag (BansheePlayer *player, const gchar *tag_name, const GValue *value)
+
+GstElement* _bp_rgvolume_new (BansheePlayer *player)
 {
-    if (strcmp (tag_name, GST_TAG_ALBUM_GAIN) == 0) {
-        player->album_gain = g_value_get_double (value);
-    } else if (strcmp (tag_name, GST_TAG_ALBUM_PEAK) == 0) {
-        player->album_peak = g_value_get_double (value);
-    } else if (strcmp (tag_name, GST_TAG_TRACK_GAIN) == 0) {
-        player->track_gain = g_value_get_double (value);
-    } else if (strcmp (tag_name, GST_TAG_TRACK_PEAK) == 0) {
-        player->track_peak = g_value_get_double (value);
+    GstElement *rgvolume = gst_element_factory_make ("rgvolume", NULL);
+
+    if (rgvolume == NULL) {
+        bp_debug ("Loading ReplayGain plugin failed.");
     }
+
+    return rgvolume;
 }
 
-void 
-_bp_replaygain_handle_state_changed (BansheePlayer *player, GstState old, GstState new, GstState pending)
+void _bp_rgvolume_print_volume(BansheePlayer *player)
 {
-    if (old == GST_STATE_READY && new == GST_STATE_NULL && 
-        pending == GST_STATE_VOID_PENDING && player->volume_scale_history_shift) {
-        
-        memmove (player->volume_scale_history + 1, 
-            player->volume_scale_history, sizeof (gdouble) * 10);
-            
-        if (player->current_scale_from_history) {
-            player->volume_scale_history[1] = 1.0;
-        }
-        
-        player->volume_scale_history[0] = 1.0;
-        player->volume_scale_history_shift = FALSE;
-        
-        player->album_gain = player->album_peak = 0.0;
-        player->track_gain = player->track_peak = 0.0;
-    } else if (old == GST_STATE_READY && new == GST_STATE_PAUSED && 
-        pending == GST_STATE_PLAYING &&  !player->volume_scale_history_shift) {
-        
-        player->volume_scale_history_shift = TRUE;
-        
-        bp_replaygain_update_pipeline (player, 
-            player->album_gain, player->album_peak, 
-            player->track_gain, player->track_peak);
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+    if (player->replaygain_enabled && (player->rgvolume != NULL)) {
+        gdouble scale;
+
+        g_object_get (G_OBJECT (player->rgvolume), "result-gain", &scale, NULL);
+
+        bp_debug ("scaled volume: %.2f (ReplayGain) * %.2f (User) = %.2f",
+                  bp_replaygain_db_to_linear(scale), player->current_volume,
+                  bp_replaygain_db_to_linear(scale) * player->current_volume);
     }
 }
 
-void
-_bp_replaygain_update_volume (BansheePlayer *player)
+void _bp_replaygain_pipeline_rebuild (BansheePlayer* player)
 {
-    GParamSpec *volume_spec;
-    GValue value = { 0, };
-    gdouble scale;
-    
-    if (player == NULL || player->playbin == NULL) {
-        return;
-    }
-    
-    scale = player->replaygain_enabled ? player->volume_scale_history[0] : 1.0;
-    
-    volume_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (player->playbin), "volume");
-    g_value_init (&value, G_TYPE_DOUBLE);
-    g_value_set_double (&value, player->current_volume * scale);
-    g_param_value_validate (volume_spec, &value);
-    
-    if (player->replaygain_enabled) {
-        bp_debug ("scaled volume: %f (ReplayGain) * %f (User) = %f", scale, player->current_volume, 
-            g_value_get_double (&value));
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+
+    g_return_if_fail (GST_IS_ELEMENT (player->before_rgvolume));
+    GstPad* srcPad = gst_element_get_static_pad(player->before_rgvolume, "src");
+
+    if (gst_pad_is_active(srcPad) && !gst_pad_is_blocked (srcPad)) {
+        gst_pad_set_blocked_async(srcPad, TRUE, &pad_block_cb, player);
+    } else if (srcPad->block_callback == NULL) {
+        pad_block_cb(srcPad, TRUE, player);
     }
-    
-    g_object_set_property (G_OBJECT (player->playbin), "volume", &value);
-    g_value_unset (&value);
 }
 
 // ---------------------------------------------------------------------------
@@ -162,7 +198,7 @@ bp_replaygain_set_enabled (BansheePlayer *player, gboolean enabled)
     g_return_if_fail (IS_BANSHEE_PLAYER (player));
     player->replaygain_enabled = enabled;
     bp_debug ("%s ReplayGain", enabled ? "Enabled" : "Disabled");
-    _bp_replaygain_update_volume (player);
+    _bp_replaygain_pipeline_rebuild (player);
 }
 
 P_INVOKE gboolean
diff --git a/libbanshee/banshee-player-replaygain.h b/libbanshee/banshee-player-replaygain.h
index b552c2d..3d80cc3 100644
--- a/libbanshee/banshee-player-replaygain.h
+++ b/libbanshee/banshee-player-replaygain.h
@@ -31,18 +31,8 @@
 
 #include "banshee-player-private.h"
 
-void _bp_replaygain_process_tag          (BansheePlayer *player, const gchar *tag_name, const GValue *value);
-void _bp_replaygain_handle_state_changed (BansheePlayer *player, GstState old, GstState new, GstState pending);
-void _bp_replaygain_update_volume        (BansheePlayer *player);
-
-static inline void
-_bp_replaygain_init (BansheePlayer *player)
-{
-    gint i;
-    for (i = 0; i < 11; i++) {
-        player->volume_scale_history[i] = 1.0;
-    }
-    
-}
+GstElement* _bp_rgvolume_new          (BansheePlayer *player);
+void        _bp_rgvolume_print_volume (BansheePlayer *player);
+void        _bp_replaygain_pipeline_rebuild (BansheePlayer* player);
 
 #endif /* _BANSHEE_PLAYER_REPLAYGAIN_H */
diff --git a/libbanshee/banshee-player.c b/libbanshee/banshee-player.c
index 4052d06..fe47eb5 100644
--- a/libbanshee/banshee-player.c
+++ b/libbanshee/banshee-player.c
@@ -124,8 +124,6 @@ bp_new ()
     
     player->mutex = g_mutex_new ();
     
-    _bp_replaygain_init (player); 
-    
     return player;
 }
 
@@ -165,6 +163,8 @@ bp_open (BansheePlayer *player, const gchar *uri)
     // Pass the request off to playbin
     g_object_set (G_OBJECT (player->playbin), "uri", uri, NULL);
     
+    player->in_gapless_transition = FALSE;
+    
     return TRUE;
 }
 
@@ -180,9 +180,11 @@ bp_stop (BansheePlayer *player, gboolean nullstate)
         state = GST_STATE_NULL;
     }
     
-    bp_debug ("bp_stop: setting state to %s", 
+    bp_debug ("bp_stop: setting state to %s",
         state == GST_STATE_NULL ? "GST_STATE_NULL" : "GST_STATE_PAUSED");
     
+    player->in_gapless_transition = FALSE;
+    
     bp_pipeline_set_state (player, state);
 }
 
@@ -195,10 +197,20 @@ bp_pause (BansheePlayer *player)
 P_INVOKE void
 bp_play (BansheePlayer *player)
 {
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
     bp_pipeline_set_state (player, GST_STATE_PLAYING);
 }
 
 P_INVOKE gboolean
+bp_set_next_track (BansheePlayer *player, const gchar *uri)
+{
+    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
+    g_return_val_if_fail (player->playbin != NULL, FALSE);
+    g_object_set (G_OBJECT (player->playbin), "uri", uri, NULL);
+    return TRUE;
+}
+
+P_INVOKE gboolean
 bp_set_position (BansheePlayer *player, guint64 time_ms)
 {
     g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
@@ -275,8 +287,11 @@ P_INVOKE void
 bp_set_volume (BansheePlayer *player, gdouble volume)
 {
     g_return_if_fail (IS_BANSHEE_PLAYER (player));
+    g_return_if_fail (GST_IS_ELEMENT (player->playbin));
+
     player->current_volume = CLAMP (volume, 0.0, 1.0);
-    _bp_replaygain_update_volume (player);
+    g_object_set (player->playbin, "volume", player->current_volume, NULL);
+    _bp_rgvolume_print_volume (player);
 }
 
 P_INVOKE gdouble
@@ -348,3 +363,15 @@ bp_get_error_quarks (GQuark *core, GQuark *library, GQuark *resource, GQuark *st
     *resource = GST_RESOURCE_ERROR;
     *stream = GST_STREAM_ERROR;
 }
+
+P_INVOKE void
+bp_set_next_track_starting_callback (BansheePlayer *player, BansheePlayerNextTrackStartingCallback cb)
+{
+    SET_CALLBACK (next_track_starting_cb);
+}
+
+P_INVOKE void
+bp_set_about_to_finish_callback (BansheePlayer *player, BansheePlayerAboutToFinishCallback cb)
+{
+    SET_CALLBACK (about_to_finish_cb);
+}
diff --git a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
index e8933b3..f37c975 100644
--- a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
@@ -29,6 +29,7 @@
 using System;
 using System.Collections;
 using System.Runtime.InteropServices;
+using System.Threading;
 using Mono.Unix;
 using Hyena;
 using Hyena.Data;
@@ -57,6 +58,8 @@ namespace Banshee.GStreamer
     internal delegate void BansheePlayerIterateCallback (IntPtr player);
     internal delegate void BansheePlayerBufferingCallback (IntPtr player, int buffering_progress);
     internal delegate void BansheePlayerVisDataCallback (IntPtr player, int channels, int samples, IntPtr data, int bands, IntPtr spectrum);
+    internal delegate void BansheePlayerNextTrackStartingCallback (IntPtr player);
+    internal delegate void BansheePlayerAboutToFinishCallback (IntPtr player);
     internal delegate IntPtr VideoPipelineSetupHandler (IntPtr player, IntPtr bus);
 
     internal delegate void GstTaggerTagFoundCallback (IntPtr player, string tagName, ref GLib.Value value);
@@ -82,11 +85,23 @@ namespace Banshee.GStreamer
         private BansheePlayerVisDataCallback vis_data_callback;
         private VideoPipelineSetupHandler video_pipeline_setup_callback;
         private GstTaggerTagFoundCallback tag_found_callback;
+        private BansheePlayerNextTrackStartingCallback next_track_starting_callback;
+#if ENABLE_GAPLESS
+        private BansheePlayerAboutToFinishCallback about_to_finish_callback;
+#endif
+
+        private bool next_track_pending;
+        private SafeUri pending_uri;
 
         private bool buffering_finished;
         private int pending_volume = -1;
         private bool xid_is_set = false;
 
+#if ENABLE_GAPLESS
+        private bool gapless_enabled;
+#endif
+        private EventWaitHandle next_track_set;
+
         private event VisualizationDataHandler data_available = null;
         public event VisualizationDataHandler DataAvailable {
             add {
@@ -138,7 +153,10 @@ namespace Banshee.GStreamer
             vis_data_callback = new BansheePlayerVisDataCallback (OnVisualizationData);
             video_pipeline_setup_callback = new VideoPipelineSetupHandler (OnVideoPipelineSetup);
             tag_found_callback = new GstTaggerTagFoundCallback (OnTagFound);
-
+            next_track_starting_callback = new BansheePlayerNextTrackStartingCallback (OnNextTrackStarting);
+#if ENABLE_GAPLESS
+            about_to_finish_callback = new BansheePlayerAboutToFinishCallback (OnAboutToFinish);
+#endif
             bp_set_eos_callback (handle, eos_callback);
 #if !WIN32
             bp_set_iterate_callback (handle, iterate_callback);
@@ -147,7 +165,10 @@ namespace Banshee.GStreamer
             bp_set_state_changed_callback (handle, state_changed_callback);
             bp_set_buffering_callback (handle, buffering_callback);
             bp_set_tag_found_callback (handle, tag_found_callback);
+            bp_set_next_track_starting_callback (handle, next_track_starting_callback);
             bp_set_video_pipeline_setup_callback (handle, video_pipeline_setup_callback);
+
+            next_track_set = new EventWaitHandle (false, EventResetMode.ManualReset);
         }
 
         protected override void Initialize ()
@@ -166,6 +187,7 @@ namespace Banshee.GStreamer
 
             InstallPreferences ();
             ReplayGainEnabled = ReplayGainEnabledSchema.Get ();
+            GaplessEnabled = GaplessEnabledSchema.Get ();
         }
 
         public override void Dispose ()
@@ -215,6 +237,30 @@ namespace Banshee.GStreamer
             bp_pause (handle);
         }
 
+        public override void SetNextTrackUri (SafeUri uri)
+        {
+            next_track_pending = false;
+            if (next_track_set.WaitOne (0)) {
+                // We're not waiting for the next track to be set.
+                // This means that we've missed the window for gapless.
+                // Save this URI to be played when we receive EOS.
+                pending_uri = uri;
+                return;
+            }
+            // If there isn't a next track for us, release the block on the about-to-finish callback.
+            if (uri == null) {
+                next_track_set.Set ();
+                return;
+            }
+            IntPtr uri_ptr = GLib.Marshaller.StringToPtrGStrdup (uri.AbsoluteUri);
+            try {
+                bp_set_next_track (handle, uri_ptr);
+            } finally {
+                GLib.Marshaller.Free (uri_ptr);
+            }
+            next_track_set.Set ();
+        }
+
         public override void VideoExpose (IntPtr window, bool direct)
         {
             bp_video_window_expose (handle, window, direct);
@@ -240,8 +286,61 @@ namespace Banshee.GStreamer
         {
             Close (false);
             OnEventChanged (PlayerEvent.EndOfStream);
+            if (!next_track_pending) {
+                OnEventChanged (PlayerEvent.RequestNextTrack);
+            } else if (pending_uri != null) {
+                Log.Warning ("[Gapless] EOS signalled while waiting for next track.");
+                Log.Warning ("[Gapless] This means that Banshee was too slow at calculating what track to play next");
+                Log.Warning ("[Gapless] If this happens frequently, please file a bug.");
+                OnStateChanged (PlayerState.Loading);
+                OpenUri (pending_uri);
+                Play ();
+                pending_uri = null;
+            } else {
+                // This should be unreachable - the RequestNextTrack event is delegated to the main thread
+                // and so blocks the bus callback from delivering the EOS message.
+                //
+                // Playback should continue as normal from here, when the RequestNextTrack message gets handled.
+                Log.Warning ("[Gapless] EndOfStream message recieved before the next track has been set");
+                Log.Warning ("[Gapless] If this happens frequently, please file a bug");
+            }
+        }
+
+        private void OnNextTrackStarting (IntPtr player)
+        {
+            if (GaplessEnabled) {
+                OnEventChanged (PlayerEvent.EndOfStream);
+                OnEventChanged (PlayerEvent.StartOfStream);
+            }
         }
 
+#if ENABLE_GAPLESS
+        private void OnAboutToFinish (IntPtr player)
+        {
+            // This is needed to make Shuffle-by-* work.
+            // Shuffle-by-* uses the LastPlayed field to determine what track in the grouping to play next.
+            // Therefore, we need to update this before requesting the next track.
+            //
+            // This will be overridden by IncrementLastPlayed () called by
+            // PlaybackControllerService's EndOfStream handler.
+            CurrentTrack.LastPlayed = DateTime.Now;
+            CurrentTrack.Save ();
+
+            next_track_set.Reset ();
+            pending_uri = null;
+            next_track_pending = true;
+
+            OnEventChanged (PlayerEvent.RequestNextTrack);
+            // Gapless playback with Playbin2 requires that the about-to-finish callback does not return until
+            // the next uri has been set.  Block here for a second or until the RequestNextTrack event has
+            // finished triggering.
+            if (!next_track_set.WaitOne (1000, false)) {
+                Log.Debug ("[Gapless] Timed out while waiting for next_track_set to be raised");
+                next_track_set.Set ();
+            }
+        }
+#endif
+
         private void OnIterate (IntPtr player)
         {
             OnEventChanged (PlayerEvent.Iterate);
@@ -499,6 +598,25 @@ namespace Banshee.GStreamer
             set { bp_replaygain_set_enabled (handle, value); }
         }
 
+        private bool GaplessEnabled {
+#if ENABLE_GAPLESS
+            get { return gapless_enabled; }
+            set
+            {
+                gapless_enabled = value;
+                if (value) {
+                    bp_set_about_to_finish_callback (handle, about_to_finish_callback);
+                } else {
+                    bp_set_about_to_finish_callback (handle, null);
+                }
+            }
+#else
+            get {return false;}
+            set {}
+#endif
+        }
+
+
 #region ISupportClutter
 
         private IntPtr clutter_video_sink;
@@ -525,6 +643,7 @@ namespace Banshee.GStreamer
             }
         }
 
+
         private IntPtr OnVideoPipelineSetup (IntPtr player, IntPtr bus)
         {
             try {
@@ -553,6 +672,7 @@ namespace Banshee.GStreamer
 #region Preferences
 
         private PreferenceBase replaygain_preference;
+        private PreferenceBase gapless_preference;
 
         private void InstallPreferences ()
         {
@@ -566,6 +686,13 @@ namespace Banshee.GStreamer
                 Catalog.GetString ("For tracks that have ReplayGain data, automatically scale (normalize) playback volume"),
                 delegate { ReplayGainEnabled = ReplayGainEnabledSchema.Get (); }
             ));
+#if ENABLE_GAPLESS
+            gapless_preference = service["general"]["misc"].Add (new SchemaPreference<bool> (GaplessEnabledSchema,
+                Catalog.GetString ("Enable _gapless playback"),
+                Catalog.GetString ("Eliminate the small playback gap on track change.  Useful for concept albums and classical music."),
+                delegate { GaplessEnabled = GaplessEnabledSchema.Get (); }
+            ));
+#endif
         }
 
         private void UninstallPreferences ()
@@ -576,7 +703,9 @@ namespace Banshee.GStreamer
             }
 
             service["general"]["misc"].Remove (replaygain_preference);
+            service["general"]["misc"].Remove (gapless_preference);
             replaygain_preference = null;
+            gapless_preference = null;
         }
 
         public static readonly SchemaEntry<bool> ReplayGainEnabledSchema = new SchemaEntry<bool> (
@@ -586,6 +715,14 @@ namespace Banshee.GStreamer
             "If ReplayGain data is present on tracks when playing, allow volume scaling"
         );
 
+        public static readonly SchemaEntry<bool> GaplessEnabledSchema = new SchemaEntry<bool> (
+            "player_engine", "gapless_playback_enabled",
+            true,
+            "Enable gapless playback",
+            "Eliminate the small playback gap on track change.  Useful for concept albums & classical music.  EXPERIMENTAL"
+        );
+
+
 #endregion
 
         [DllImport ("libbanshee.dll")]
@@ -627,6 +764,16 @@ namespace Banshee.GStreamer
             GstTaggerTagFoundCallback cb);
 
         [DllImport ("libbanshee.dll")]
+        private static extern void bp_set_next_track_starting_callback (HandleRef player,
+            BansheePlayerNextTrackStartingCallback cb);
+
+#if ENABLE_GAPLESS
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_set_about_to_finish_callback (HandleRef player,
+            BansheePlayerAboutToFinishCallback cb);
+#endif
+
+        [DllImport ("libbanshee.dll")]
         private static extern bool bp_open (HandleRef player, IntPtr uri);
 
         [DllImport ("libbanshee.dll")]
@@ -639,6 +786,9 @@ namespace Banshee.GStreamer
         private static extern void bp_play (HandleRef player);
 
         [DllImport ("libbanshee.dll")]
+        private static extern bool bp_set_next_track (HandleRef player, IntPtr uri);
+
+        [DllImport ("libbanshee.dll")]
         private static extern void bp_set_volume (HandleRef player, double volume);
 
         [DllImport("libbanshee.dll")]
diff --git a/src/Backends/Banshee.GStreamer/Makefile.am b/src/Backends/Banshee.GStreamer/Makefile.am
index 321b270..118e525 100644
--- a/src/Backends/Banshee.GStreamer/Makefile.am
+++ b/src/Backends/Banshee.GStreamer/Makefile.am
@@ -16,3 +16,7 @@ include $(top_srcdir)/build/build.mk
 
 EXTRA_DIST += Banshee.GStreamer.dll.config
 module_SCRIPTS += Banshee.GStreamer.dll.config
+
+if ENABLE_GAPLESS
+BUILD_DEFINES="-define:ENABLE_GAPLESS"
+endif
\ No newline at end of file
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
index 76a2ec9..d5006cb 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
@@ -47,6 +47,8 @@ namespace Banshee.MediaEngine
 
         private TrackInfo current_track;
         private SafeUri current_uri;
+        private TrackInfo pending_track;
+        private SafeUri pending_uri;
         private PlayerState current_state = PlayerState.NotReady;
         private PlayerState last_state = PlayerState.NotReady;
 
@@ -98,6 +100,42 @@ namespace Banshee.MediaEngine
             HandleOpen (uri);
         }
 
+        public void SetNextTrack (TrackInfo track)
+        {
+            pending_track = track;
+            pending_uri = track != null ? track.Uri : null;
+
+            HandleNextTrack (pending_uri);
+        }
+
+        public void SetNextTrack (SafeUri uri)
+        {
+            pending_uri = uri;
+            pending_track = new UnknownTrackInfo (uri);
+
+            HandleNextTrack (uri);
+        }
+
+        private void HandleNextTrack (SafeUri uri)
+        {
+            if (current_state != PlayerState.Playing) {
+                // Pre-buffering the next track only makes sense when we're currently playing
+                // Instead, just open.
+                if (uri != null) {
+                    HandleOpen (uri);
+                    Play ();
+                }
+                return;
+            }
+
+            try {
+                // Setting the next track doesn't change the player state.
+                SetNextTrackUri (uri);
+            } catch (Exception e) {
+                Log.Exception ("Failed to pre-buffer next track", e);
+            }
+        }
+
         private void HandleOpen (SafeUri uri)
         {
             if (current_state != PlayerState.Idle && current_state != PlayerState.NotReady && current_state != PlayerState.Contacting) {
@@ -117,6 +155,13 @@ namespace Banshee.MediaEngine
 
         public abstract void Pause ();
 
+        public virtual void SetNextTrackUri (SafeUri uri)
+        {
+            // Opening files on SetNextTrack is a sane default behaviour.
+            // This only wants to be overridden if the PlayerEngine sends out RequestNextTrack signals before EoS
+            OpenUri (uri);
+        }
+
         public virtual void VideoExpose (IntPtr displayContext, bool direct)
         {
             throw new NotImplementedException ("Engine must implement VideoExpose since this method only gets called when SupportsVideo is true");
@@ -168,6 +213,13 @@ namespace Banshee.MediaEngine
 
         protected virtual void OnEventChanged (PlayerEventArgs args)
         {
+            if (args.Event == PlayerEvent.StartOfStream && pending_track != null) {
+                Log.DebugFormat ("OnEventChanged called with StartOfStream.  Replacing current_track: \"{0}\" with pending_track: \"{1}\"", current_track.DisplayTrackTitle, pending_track.DisplayTrackTitle);
+                current_track = pending_track;
+                current_uri = pending_uri;
+                pending_track = null;
+                pending_uri = null;
+            }
             if (ThreadAssist.InMainThread) {
                 RaiseEventChanged (args);
             } else {
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
index 300fd64..1d46a58 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
@@ -229,6 +229,10 @@ namespace Banshee.MediaEngine
                 }
             }
 
+            if (args.Event == PlayerEvent.StartOfStream) {
+                incremented_last_played = false;
+            }
+
             RaiseEvent (args);
 
             // Do not raise iterate across DBus to avoid so many calls;
@@ -286,6 +290,46 @@ namespace Banshee.MediaEngine
             OpenCheck (new SafeUri (uri));
         }
 
+        public void SetNextTrack (TrackInfo track)
+        {
+            if (track != null && EnsureActiveEngineCanPlay (track.Uri)) {
+                active_engine.SetNextTrack (track);
+            } else {
+                active_engine.SetNextTrack ((TrackInfo) null);
+            }
+        }
+
+        public void SetNextTrack (SafeUri uri)
+        {
+            if (EnsureActiveEngineCanPlay (uri)) {
+                active_engine.SetNextTrack (uri);
+            } else {
+                active_engine.SetNextTrack ((SafeUri) null);
+            }
+        }
+
+        private bool EnsureActiveEngineCanPlay (SafeUri uri)
+        {
+            if (uri == null) {
+                // No engine can play the null URI.
+                return false;
+            }
+            if (active_engine != FindSupportingEngine (uri)) {
+                if (active_engine.CurrentState == PlayerState.Playing) {
+                    // If we're currently playing then we can't switch engines now.
+                    // We can't ensure the active engine can play this URI.
+                    return false;
+                } else {
+                    // If we're not playing, we can switch the active engine to
+                    // something that will play this URI.
+                    SwitchToEngine (FindSupportingEngine (uri));
+                    CheckPending ();
+                    return true;
+                }
+            }
+            return true;
+        }
+
         public void OpenPlay (TrackInfo track)
         {
             OpenPlay (track, true);
@@ -332,17 +376,14 @@ namespace Banshee.MediaEngine
                 return;
             }
 
-            IncrementLastPlayed ();
-
-            FindSupportingEngine (uri);
+            PlayerEngine supportingEngine = FindSupportingEngine (uri);
+            SwitchToEngine (supportingEngine);
             CheckPending ();
 
             if (track != null) {
                 active_engine.Open (track);
-                incremented_last_played = false;
             } else if (uri != null) {
                 active_engine.Open (uri);
-                incremented_last_played = false;
             }
 
             if (play) {
@@ -364,34 +405,39 @@ namespace Banshee.MediaEngine
             }
         }
 
-        private void FindSupportingEngine (SafeUri uri)
+        private PlayerEngine FindSupportingEngine (SafeUri uri)
         {
             foreach (PlayerEngine engine in engines) {
                 foreach (string extension in engine.ExplicitDecoderCapabilities) {
                     if (!uri.AbsoluteUri.EndsWith (extension)) {
                         continue;
-                    } else if (active_engine != engine) {
-                        Close ();
-                        pending_engine = engine;
-                        Log.DebugFormat ("Switching engine to: {0}", engine.GetType ());
                     }
-                    return;
+                    return engine;
                 }
             }
 
             foreach (PlayerEngine engine in engines) {
                 foreach (string scheme in engine.SourceCapabilities) {
                     bool supported = scheme == uri.Scheme;
-                    if (supported && active_engine != engine) {
-                        Close ();
-                        pending_engine = engine;
-                        Log.DebugFormat ("Switching engine to: {0}", engine.GetType ());
-                        return;
-                    } else if (supported) {
-                        return;
+                    if (supported) {
+                        return engine;
                     }
                 }
             }
+            // If none of our engines support this URI, return the currently active one.
+            // There doesn't seem to be anything better to do.
+            return active_engine;
+        }
+
+        private bool SwitchToEngine (PlayerEngine switchTo)
+        {
+            if (active_engine != switchTo) {
+                Close ();
+                pending_engine = switchTo;
+                Log.DebugFormat ("Switching engine to: {0}", switchTo.GetType ());
+                return true;
+            }
+            return false;
         }
 
         public void Close ()
@@ -642,7 +688,8 @@ namespace Banshee.MediaEngine
             | PlayerEvent.Error
             | PlayerEvent.Volume
             | PlayerEvent.Metadata
-            | PlayerEvent.TrackInfoUpdated;
+            | PlayerEvent.TrackInfoUpdated
+            | PlayerEvent.RequestNextTrack;
 
         private const PlayerEvent event_default_mask = event_all_mask & ~PlayerEvent.Iterate;
 
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs
index 96b1b79..a5c714f 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs
@@ -108,7 +108,8 @@ namespace Banshee.MediaEngine
         Error = 64,
         Volume = 128,
         Metadata = 256,
-        TrackInfoUpdated = 512
+        TrackInfoUpdated = 512,
+        RequestNextTrack = 1024
     }
 
     public enum PlayerState
diff --git a/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs b/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs
index 1984cc7..217009e 100644
--- a/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs
+++ b/src/Core/Banshee.Services/Banshee.Metadata/SaveTrackMetadataJob.cs
@@ -73,6 +73,12 @@ namespace Banshee.Metadata
         {
             DatabaseTrackInfo track = DatabaseTrackInfo.Provider.Load (reader.Reader);
 
+            var write_delay = track.DateUpdated.AddSeconds (2) - DateTime.Now;
+            if (write_delay.TotalMilliseconds > 0) {
+                System.Threading.Thread.Sleep (write_delay);
+                return;
+            }
+
             bool wrote = false;
             bool renamed = false;
             try {
diff --git a/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs b/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs
index 4d793c7..9825f8a 100644
--- a/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs
+++ b/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs
@@ -31,7 +31,7 @@ namespace Banshee.PlaybackController
     public interface IBasicPlaybackController
     {
         bool First ();
-        bool Next (bool restart);
+        bool Next (bool restart, bool userRequested);
         bool Previous (bool restart);
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs b/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
index 4c8d212..81115b8 100644
--- a/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
+++ b/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
@@ -90,6 +90,7 @@ namespace Banshee.PlaybackController
             player_engine = ServiceManager.PlayerEngine;
             player_engine.PlayWhenIdleRequest += OnPlayerEnginePlayWhenIdleRequest;
             player_engine.ConnectEvent (OnPlayerEvent,
+                PlayerEvent.RequestNextTrack |
                 PlayerEvent.EndOfStream |
                 PlayerEvent.StartOfStream |
                 PlayerEvent.StateChange |
@@ -164,6 +165,9 @@ namespace Banshee.PlaybackController
                         transition_track_started = true;
                     }
                     break;
+                case PlayerEvent.RequestNextTrack:
+                    RequestTrackHandler ();
+                    break;
             }
         }
 
@@ -177,12 +181,18 @@ namespace Banshee.PlaybackController
 
         private bool EosTransition ()
         {
+            player_engine.IncrementLastPlayed ();
+            return true;
+        }
+
+        private bool RequestTrackHandler ()
+        {
             if (!StopWhenFinished) {
                 if (RepeatMode == PlaybackRepeatMode.RepeatSingle) {
                     QueuePlayTrack ();
                 } else {
                     last_was_skipped = false;
-                    Next ();
+                    Next (RepeatMode == PlaybackRepeatMode.RepeatAll, false);
                 }
             } else {
                 OnStopped ();
@@ -211,21 +221,28 @@ namespace Banshee.PlaybackController
 
         public void Next ()
         {
-            Next (RepeatMode == PlaybackRepeatMode.RepeatAll);
+            Next (RepeatMode == PlaybackRepeatMode.RepeatAll, true);
         }
 
         public void Next (bool restart)
         {
+            Next (restart, true);
+        }
+
+        public void Next (bool restart, bool userRequested)
+        {
             CancelErrorTransition ();
 
             Source = NextSource;
             raise_started_after_transition = true;
 
-            player_engine.IncrementLastPlayed ();
+            if (userRequested) {
+                player_engine.IncrementLastPlayed ();
+            }
 
-            if (Source is IBasicPlaybackController && ((IBasicPlaybackController)Source).Next (restart)) {
+            if (Source is IBasicPlaybackController && ((IBasicPlaybackController)Source).Next (restart, userRequested)) {
             } else {
-                ((IBasicPlaybackController)this).Next (restart);
+                ((IBasicPlaybackController)this).Next (restart, userRequested);
             }
 
             OnTransition ();
@@ -278,35 +295,25 @@ namespace Banshee.PlaybackController
                 if (ShuffleMode == "off") {
                     player_engine.OpenPlay (Source.TrackModel[0]);
                 } else {
-                    ((IBasicPlaybackController)this).Next (false);
+                    ((IBasicPlaybackController)this).Next (false, true);
                 }
             }
             return true;
         }
 
-        bool IBasicPlaybackController.Next (bool restart)
+        bool IBasicPlaybackController.Next (bool restart, bool userRequested)
         {
-            TrackInfo tmp_track = CurrentTrack;
-
-            if (next_stack.Count > 0) {
-                CurrentTrack = next_stack.Pop ();
-                if (tmp_track != null) {
-                    previous_stack.Push (tmp_track);
-                }
-            } else {
-                TrackInfo next_track = QueryTrack (Direction.Next, restart);
-                if (next_track != null) {
-                    if (tmp_track != null) {
-                        previous_stack.Push (tmp_track);
-                    }
-                } else {
-                    return true;
-                }
-
-                CurrentTrack = next_track;
+            if (CurrentTrack != null) {
+                previous_stack.Push (CurrentTrack);
             }
 
-            QueuePlayTrack ();
+            CurrentTrack = CalcNextTrack (Direction.Next, restart);
+            if (!userRequested) {
+                // A RequestNextTrack event should always result in SetNextTrack being called.  null is acceptable.
+                player_engine.SetNextTrack (CurrentTrack);
+            } else if (CurrentTrack != null) {
+                QueuePlayTrack ();
+            }
             return true;
         }
 
@@ -316,21 +323,28 @@ namespace Banshee.PlaybackController
                 next_stack.Push (current_track);
             }
 
-            if (previous_stack.Count > 0) {
-                CurrentTrack = previous_stack.Pop ();
-            } else {
-                TrackInfo track = CurrentTrack = QueryTrack (Direction.Previous, restart);
-                if (track != null) {
-                    CurrentTrack = track;
-                } else {
-                    return true;
-                }
+            CurrentTrack = CalcNextTrack (Direction.Previous, restart);
+            if (CurrentTrack != null) {
+                QueuePlayTrack ();
             }
 
-            QueuePlayTrack ();
             return true;
         }
 
+        private TrackInfo CalcNextTrack (Direction direction, bool restart)
+        {
+            if (direction == Direction.Previous) {
+                if (previous_stack.Count > 0) {
+                    return previous_stack.Pop ();
+                }
+            } else if (direction == Direction.Next) {
+                if (next_stack.Count > 0) {
+                    return next_stack.Pop ();
+                }
+            }
+            return QueryTrack (direction, restart);
+        }
+
         private TrackInfo QueryTrack (Direction direction, bool restart)
         {
             Log.DebugFormat ("Querying model for track to play in {0}:{1} mode", ShuffleMode, direction);
diff --git a/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs b/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs
index 8581772..f2d993b 100644
--- a/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs
+++ b/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs
@@ -277,8 +277,20 @@ namespace Banshee.InternetRadio
             return false;
         }
 
-        public bool Next (bool restart)
+        public bool Next (bool restart, bool userRequested)
         {
+            /*
+             * TODO: It should be technically possible to handle userRequested=False
+             * correctly here, but the current implementation is quite hostile.
+             * For the moment, just SetNextTrack (null), and go on to OpenPlay if
+             * the engine isn't currently playing.
+             */
+            if (!userRequested) {
+                ServiceManager.PlayerEngine.SetNextTrack ((SafeUri)null);
+                if (ServiceManager.PlayerEngine.IsPlaying ()) {
+                    return true;
+                }
+            }
             RadioTrackInfo radio_track = ServiceManager.PlaybackController.CurrentTrack as RadioTrackInfo;
             if (radio_track != null && radio_track.PlayNextStream ()) {
                 return true;
diff --git a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
index 2aeffce..746349b 100644
--- a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
+++ b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
@@ -294,17 +294,24 @@ namespace Banshee.Lastfm.Radio
 
         bool IBasicPlaybackController.First ()
         {
-            return ((IBasicPlaybackController)this).Next (false);
+            return ((IBasicPlaybackController)this).Next (false, true);
         }
 
         private bool playback_requested;
-        bool IBasicPlaybackController.Next (bool restart)
+        bool IBasicPlaybackController.Next (bool restart, bool userRequested)
         {
             TrackInfo next = NextTrack;
-            if (next != null) {
-                ServiceManager.PlayerEngine.OpenPlay (next);
-            }  else {
-                playback_requested = true;
+            if (userRequested) {
+                if (next != null) {
+                    ServiceManager.PlayerEngine.OpenPlay (next);
+                }  else {
+                    playback_requested = true;
+                }
+            } else {
+                // We want to unconditionally SetNextTrack.
+                // Passing null is OK.
+                ServiceManager.PlayerEngine.SetNextTrack (next);
+                playback_requested = next == null;
             }
             return true;
         }
@@ -372,7 +379,7 @@ namespace Banshee.Lastfm.Radio
 
                             if (playback_requested) {
                                 if (this == ServiceManager.PlaybackController.Source ) {
-                                    ((IBasicPlaybackController)this).Next (false);
+                                    ((IBasicPlaybackController)this).Next (false, true);
                                 }
                                 playback_requested = false;
                             }
diff --git a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingSource.cs b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingSource.cs
index 5f68029..04ee475 100644
--- a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingSource.cs
+++ b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingSource.cs
@@ -96,7 +96,8 @@ namespace Banshee.NowPlaying
                 ServiceManager.SourceManager.SetActiveSource (this);
             }
 
-            if ((current_track.MediaAttributes & TrackMediaAttributes.VideoStream) != 0) {
+            if (current_track != null &&
+                (current_track.MediaAttributes & TrackMediaAttributes.VideoStream) != 0) {
                 OnUserNotifyUpdated ();
             }
         }
diff --git a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
index 9a9a78b..5b63193 100644
--- a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
+++ b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
@@ -535,10 +535,10 @@ namespace Banshee.PlayQueue
 
         bool IBasicPlaybackController.First ()
         {
-            return ((IBasicPlaybackController)this).Next (false);
+            return ((IBasicPlaybackController)this).Next (false, true);
         }
 
-        bool IBasicPlaybackController.Next (bool restart)
+        bool IBasicPlaybackController.Next (bool restart, bool userRequested)
         {
             if (current_track != null && ServiceManager.PlayerEngine.CurrentTrack == current_track) {
                 int index = TrackModel.IndexOf (current_track) + 1;
@@ -549,14 +549,21 @@ namespace Banshee.PlayQueue
                 ServiceManager.PlaybackController.Source = PriorSource;
                 if (was_playing) {
                     ServiceManager.PlaybackController.PriorTrack = prior_playback_track;
-                    ServiceManager.PlaybackController.Next (restart);
+                    ServiceManager.PlaybackController.Next (restart, userRequested);
                 } else {
+                    if (!userRequested) {
+                        ServiceManager.PlayerEngine.SetNextTrack ((TrackInfo)null);
+                    }
                     ServiceManager.PlayerEngine.Close ();
                 }
                 return true;
             }
 
-            ServiceManager.PlayerEngine.OpenPlay (current_track);
+            if (userRequested) {
+                ServiceManager.PlayerEngine.OpenPlay (current_track);
+            } else {
+                ServiceManager.PlayerEngine.SetNextTrack (current_track);
+            }
             return true;
         }
 
